SEC-3031: DelegatingSecurityContext(Runnable|Callable) only modify SecurityContext on new Thread

Modifying the SecurityContext on the same Thread can cause issues. For example, with a
RejectedExecutionHandler the SecurityContext may be cleared out on the original Thread.

This change modifies both the DelegatingSecurityContextRunnable and DelegatingSecurityContextCallable to,
by default, only modify the SecurityContext if they are invoked on a new Thread. The behavior can be changed
by setting the property enableOnOrigionalThread to true.
This commit is contained in:
Rob Winch
2015-07-22 15:33:59 -05:00
parent 113b61e3a0
commit 117f892c91
5 changed files with 146 additions and 22 deletions

View File

@@ -19,9 +19,17 @@ import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.Assert;
/**
* Wraps a delegate {@link Callable} with logic for setting up a {@link SecurityContext}
* before invoking the delegate {@link Callable} and then removing the
* {@link SecurityContext} after the delegate has completed.
* <p>
* Wraps a delegate {@link Callable} with logic for setting up a
* {@link SecurityContext} before invoking the delegate {@link Callable} and
* then removing the {@link SecurityContext} after the delegate has completed.
* </p>
* <p>
* By default the {@link SecurityContext} is only setup if {@link #call()} is
* invoked on a separate {@link Thread} than the
* {@link DelegatingSecurityContextCallable} was created on. This can be
* overridden by setting {@link #setEnableOnOriginalThread(boolean)} to true.
* </p>
*
* @author Rob Winch
* @since 3.2
@@ -32,6 +40,10 @@ public final class DelegatingSecurityContextCallable<V> implements Callable<V> {
private final SecurityContext securityContext;
private final Thread originalThread;
private boolean enableOnOriginalThread;
/**
* Creates a new {@link DelegatingSecurityContextCallable} with a specific
* {@link SecurityContext}.
@@ -46,6 +58,7 @@ public final class DelegatingSecurityContextCallable<V> implements Callable<V> {
Assert.notNull(securityContext, "securityContext cannot be null");
this.delegate = delegate;
this.securityContext = securityContext;
this.originalThread = Thread.currentThread();
}
/**
@@ -58,7 +71,27 @@ public final class DelegatingSecurityContextCallable<V> implements Callable<V> {
this(delegate, SecurityContextHolder.getContext());
}
/**
* Determines if the SecurityContext should be transfered if {@link #call()}
* is invoked on the same {@link Thread} the
* {@link DelegatingSecurityContextCallable} was created on.
*
* @param enableOnOriginalThread
* if false (default), will only transfer the
* {@link SecurityContext} if {@link #call()} is invoked on a
* different {@link Thread} than the
* {@link DelegatingSecurityContextCallable} was created on.
*
* @since 4.0.2
*/
public void setEnableOnOriginalThread(boolean enableOnOriginalThread) {
this.enableOnOriginalThread = enableOnOriginalThread;
}
public V call() throws Exception {
if(!enableOnOriginalThread && originalThread == Thread.currentThread()) {
return delegate.call();
}
try {
SecurityContextHolder.setContext(securityContext);
return delegate.call();

View File

@@ -17,9 +17,17 @@ import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.Assert;
/**
* <p>
* Wraps a delegate {@link Runnable} with logic for setting up a {@link SecurityContext}
* before invoking the delegate {@link Runnable} and then removing the
* {@link SecurityContext} after the delegate has completed.
* </p>
* <p>
* By default the {@link SecurityContext} is only setup if {@link #run()} is
* invoked on a separate {@link Thread} than the
* {@link DelegatingSecurityContextRunnable} was created on. This can be
* overridden by setting {@link #setEnableOnOriginalThread(boolean)} to true.
* </p>
*
* @author Rob Winch
* @since 3.2
@@ -30,6 +38,10 @@ public final class DelegatingSecurityContextRunnable implements Runnable {
private final SecurityContext securityContext;
private final Thread originalThread;
private boolean enableOnOriginalThread;
/**
* Creates a new {@link DelegatingSecurityContextRunnable} with a specific
* {@link SecurityContext}.
@@ -44,6 +56,7 @@ public final class DelegatingSecurityContextRunnable implements Runnable {
Assert.notNull(securityContext, "securityContext cannot be null");
this.delegate = delegate;
this.securityContext = securityContext;
this.originalThread = Thread.currentThread();
}
/**
@@ -56,7 +69,27 @@ public final class DelegatingSecurityContextRunnable implements Runnable {
this(delegate, SecurityContextHolder.getContext());
}
/**
* Determines if the SecurityContext should be transfered if {@link #call()}
* is invoked on the same {@link Thread} the
* {@link DelegatingSecurityContextCallable} was created on.
*
* @param enableOnOriginalThread
* if false (default), will only transfer the
* {@link SecurityContext} if {@link #call()} is invoked on a
* different {@link Thread} than the
* {@link DelegatingSecurityContextCallable} was created on.
* @since 4.0.2
*/
public void setEnableOnOriginalThread(boolean enableOnOriginalThread) {
this.enableOnOriginalThread = enableOnOriginalThread;
}
public void run() {
if(!enableOnOriginalThread && originalThread == Thread.currentThread()) {
delegate.run();
return;
}
try {
SecurityContextHolder.setContext(securityContext);
delegate.run();