浏览代码

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.
Rob Winch 10 年之前
父节点
当前提交
23de257508

+ 36 - 2
core/src/main/java/org/springframework/security/concurrent/DelegatingSecurityContextCallable.java

@@ -19,8 +19,17 @@ import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.util.Assert;
 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
  * @author Rob Winch
  * @since 3.2
  * @since 3.2
@@ -31,6 +40,10 @@ public final class DelegatingSecurityContextCallable<V> implements Callable<V> {
 
 
     private final SecurityContext securityContext;
     private final SecurityContext securityContext;
 
 
+    private final Thread originalThread;
+
+    private boolean enableOnOriginalThread;
+
     /**
     /**
      * Creates a new {@link DelegatingSecurityContextCallable} with a specific {@link SecurityContext}.
      * Creates a new {@link DelegatingSecurityContextCallable} with a specific {@link SecurityContext}.
      * @param delegate the delegate {@link DelegatingSecurityContextCallable} to run with the specified
      * @param delegate the delegate {@link DelegatingSecurityContextCallable} to run with the specified
@@ -43,6 +56,7 @@ public final class DelegatingSecurityContextCallable<V> implements Callable<V> {
         Assert.notNull(securityContext, "securityContext cannot be null");
         Assert.notNull(securityContext, "securityContext cannot be null");
         this.delegate = delegate;
         this.delegate = delegate;
         this.securityContext = securityContext;
         this.securityContext = securityContext;
+        this.originalThread = Thread.currentThread();
     }
     }
 
 
     /**
     /**
@@ -54,7 +68,27 @@ public final class DelegatingSecurityContextCallable<V> implements Callable<V> {
         this(delegate, SecurityContextHolder.getContext());
         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 {
     public V call() throws Exception {
+        if(!enableOnOriginalThread && originalThread == Thread.currentThread()) {
+            return delegate.call();
+        }
         try {
         try {
             SecurityContextHolder.setContext(securityContext);
             SecurityContextHolder.setContext(securityContext);
             return delegate.call();
             return delegate.call();

+ 36 - 2
core/src/main/java/org/springframework/security/concurrent/DelegatingSecurityContextRunnable.java

@@ -17,8 +17,17 @@ import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.util.Assert;
 import org.springframework.util.Assert;
 
 
 /**
 /**
- * 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>
+ * 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
  * @author Rob Winch
  * @since 3.2
  * @since 3.2
@@ -29,6 +38,10 @@ public final class DelegatingSecurityContextRunnable implements Runnable {
 
 
     private final SecurityContext securityContext;
     private final SecurityContext securityContext;
 
 
+    private final Thread originalThread;
+
+    private boolean enableOnOriginalThread;
+
     /**
     /**
      * Creates a new {@link DelegatingSecurityContextRunnable} with a specific {@link SecurityContext}.
      * Creates a new {@link DelegatingSecurityContextRunnable} with a specific {@link SecurityContext}.
      * @param delegate the delegate {@link Runnable} to run with the specified {@link SecurityContext}. Cannot be null.
      * @param delegate the delegate {@link Runnable} to run with the specified {@link SecurityContext}. Cannot be null.
@@ -40,6 +53,7 @@ public final class DelegatingSecurityContextRunnable implements Runnable {
         Assert.notNull(securityContext, "securityContext cannot be null");
         Assert.notNull(securityContext, "securityContext cannot be null");
         this.delegate = delegate;
         this.delegate = delegate;
         this.securityContext = securityContext;
         this.securityContext = securityContext;
+        this.originalThread = Thread.currentThread();
     }
     }
 
 
     /**
     /**
@@ -51,7 +65,27 @@ public final class DelegatingSecurityContextRunnable implements Runnable {
         this(delegate, SecurityContextHolder.getContext());
         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() {
     public void run() {
+        if(!enableOnOriginalThread && originalThread == Thread.currentThread()) {
+            delegate.run();
+            return;
+        }
         try {
         try {
             SecurityContextHolder.setContext(securityContext);
             SecurityContextHolder.setContext(securityContext);
             delegate.run();
             delegate.run();

+ 42 - 10
core/src/test/java/org/springframework/security/concurrent/DelegatingSecurityContextCallableTests.java

@@ -17,6 +17,9 @@ import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 import static org.mockito.Mockito.when;
 
 
 import java.util.concurrent.Callable;
 import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
 
 
 import org.junit.After;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Before;
@@ -45,6 +48,8 @@ public class DelegatingSecurityContextCallableTests {
 
 
     private Callable<Object> callable;
     private Callable<Object> callable;
 
 
+    private ExecutorService executor;
+
     @Before
     @Before
     @SuppressWarnings("serial")
     @SuppressWarnings("serial")
     public void setUp() throws Exception {
     public void setUp() throws Exception {
@@ -55,6 +60,7 @@ public class DelegatingSecurityContextCallableTests {
                 return super.answer(invocation);
                 return super.answer(invocation);
             }
             }
         });
         });
+        executor = Executors.newFixedThreadPool(1);
     }
     }
 
 
     @After
     @After
@@ -88,15 +94,34 @@ public class DelegatingSecurityContextCallableTests {
 
 
     @Test
     @Test
     public void call() throws Exception {
     public void call() throws Exception {
-        callable = new DelegatingSecurityContextCallable<Object>(delegate, securityContext);
-        assertWrapped(callable.call());
+        callable = new DelegatingSecurityContextCallable<Object>(delegate,
+                securityContext);
+        assertWrapped(callable);
     }
     }
 
 
     @Test
     @Test
     public void callDefaultSecurityContext() throws Exception {
     public void callDefaultSecurityContext() throws Exception {
         SecurityContextHolder.setContext(securityContext);
         SecurityContextHolder.setContext(securityContext);
         callable = new DelegatingSecurityContextCallable<Object>(delegate);
         callable = new DelegatingSecurityContextCallable<Object>(delegate);
-        SecurityContextHolder.clearContext(); // ensure callable is what sets up the SecurityContextHolder
+        SecurityContextHolder.clearContext(); // ensure callable is what sets up the
+                                                // SecurityContextHolder
+        assertWrapped(callable);
+    }
+
+    // SEC-3031
+    @Test
+    public void callOnSameThread() throws Exception {
+        callable = new DelegatingSecurityContextCallable<Object>(delegate,
+                securityContext);
+        securityContext = SecurityContextHolder.createEmptyContext();
+        assertWrapped(callable.call());
+    }
+
+    @Test
+    public void callOnSameThreadExplicitlyEnabled() throws Exception {
+        DelegatingSecurityContextCallable<Object> callable = new DelegatingSecurityContextCallable<Object>(delegate,
+                securityContext);
+        callable.setEnableOnOriginalThread(true);
         assertWrapped(callable.call());
         assertWrapped(callable.call());
     }
     }
 
 
@@ -116,14 +141,15 @@ public class DelegatingSecurityContextCallableTests {
     public void createNullSecurityContext() throws Exception {
     public void createNullSecurityContext() throws Exception {
         SecurityContextHolder.setContext(securityContext);
         SecurityContextHolder.setContext(securityContext);
         callable = DelegatingSecurityContextCallable.create(delegate, null);
         callable = DelegatingSecurityContextCallable.create(delegate, null);
-        SecurityContextHolder.clearContext(); // ensure callable is what sets up the SecurityContextHolder
-        assertWrapped(callable.call());
+        SecurityContextHolder.clearContext(); // ensure callable is what sets up the
+                                                // SecurityContextHolder
+        assertWrapped(callable);
     }
     }
 
 
     @Test
     @Test
     public void create() throws Exception {
     public void create() throws Exception {
         callable = DelegatingSecurityContextCallable.create(delegate, securityContext);
         callable = DelegatingSecurityContextCallable.create(delegate, securityContext);
-        assertWrapped(callable.call());
+        assertWrapped(callable);
     }
     }
 
 
     // --- toString
     // --- toString
@@ -131,13 +157,19 @@ public class DelegatingSecurityContextCallableTests {
     // SEC-2682
     // SEC-2682
     @Test
     @Test
     public void toStringDelegates() {
     public void toStringDelegates() {
-        callable = new DelegatingSecurityContextCallable<Object>(delegate, securityContext);
+        callable = new DelegatingSecurityContextCallable<Object>(delegate,
+                securityContext);
         assertThat(callable.toString()).isEqualTo(delegate.toString());
         assertThat(callable.toString()).isEqualTo(delegate.toString());
     }
     }
 
 
-    private void assertWrapped(Object actualResult) throws Exception {
-        assertThat(actualResult).isEqualTo(callableResult);
+    private void assertWrapped(Callable<Object> callable) throws Exception {
+        Future<Object> submit = executor.submit(callable);
+        assertWrapped(submit.get());
+    }
+
+    private void assertWrapped(Object callableResult) throws Exception {
         verify(delegate).call();
         verify(delegate).call();
-        assertThat(SecurityContextHolder.getContext()).isEqualTo(SecurityContextHolder.createEmptyContext());
+        assertThat(SecurityContextHolder.getContext()).isEqualTo(
+                SecurityContextHolder.createEmptyContext());
     }
     }
 }
 }

+ 49 - 16
core/src/test/java/org/springframework/security/concurrent/DelegatingSecurityContextRunnableTests.java

@@ -16,6 +16,10 @@ import static org.fest.assertions.Assertions.assertThat;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verify;
 
 
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
 import org.junit.After;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.Test;
@@ -24,6 +28,8 @@ import org.mockito.Mock;
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.runners.MockitoJUnitRunner;
 import org.mockito.runners.MockitoJUnitRunner;
 import org.mockito.stubbing.Answer;
 import org.mockito.stubbing.Answer;
+import org.springframework.core.task.SyncTaskExecutor;
+import org.springframework.core.task.support.ExecutorServiceAdapter;
 import org.springframework.security.core.context.SecurityContext;
 import org.springframework.security.core.context.SecurityContext;
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.security.core.context.SecurityContextHolder;
 
 
@@ -43,6 +49,8 @@ public class DelegatingSecurityContextRunnableTests {
 
 
     private Runnable runnable;
     private Runnable runnable;
 
 
+    private ExecutorService executor;
+
     @Before
     @Before
     public void setUp() throws Exception {
     public void setUp() throws Exception {
         doAnswer(new Answer<Object>() {
         doAnswer(new Answer<Object>() {
@@ -50,8 +58,9 @@ public class DelegatingSecurityContextRunnableTests {
                 assertThat(SecurityContextHolder.getContext()).isEqualTo(securityContext);
                 assertThat(SecurityContextHolder.getContext()).isEqualTo(securityContext);
                 return null;
                 return null;
             }
             }
-        })
-        .when(delegate).run();
+        }).when(delegate).run();
+
+        executor = Executors.newFixedThreadPool(1);
     }
     }
 
 
     @After
     @After
@@ -86,17 +95,35 @@ public class DelegatingSecurityContextRunnableTests {
     @Test
     @Test
     public void call() throws Exception {
     public void call() throws Exception {
         runnable = new DelegatingSecurityContextRunnable(delegate, securityContext);
         runnable = new DelegatingSecurityContextRunnable(delegate, securityContext);
-        runnable.run();
-        assertWrapped();
+        assertWrapped(runnable);
     }
     }
 
 
     @Test
     @Test
     public void callDefaultSecurityContext() throws Exception {
     public void callDefaultSecurityContext() throws Exception {
         SecurityContextHolder.setContext(securityContext);
         SecurityContextHolder.setContext(securityContext);
         runnable = new DelegatingSecurityContextRunnable(delegate);
         runnable = new DelegatingSecurityContextRunnable(delegate);
-        SecurityContextHolder.clearContext(); // ensure runnable is what sets up the SecurityContextHolder
-        runnable.run();
-        assertWrapped();
+        SecurityContextHolder.clearContext(); // ensure runnable is what sets up the
+                                                // SecurityContextHolder
+        assertWrapped(runnable);
+    }
+
+    // SEC-3031
+    @Test
+    public void callOnSameThread() throws Exception {
+        executor = synchronousExecutor();
+        runnable = new DelegatingSecurityContextRunnable(delegate,
+                securityContext);
+        securityContext = SecurityContextHolder.createEmptyContext();
+        assertWrapped(runnable);
+    }
+
+    @Test
+    public void callOnSameThreadExplicitlyEnabled() throws Exception {
+        executor = synchronousExecutor();
+        DelegatingSecurityContextRunnable runnable = new DelegatingSecurityContextRunnable(delegate,
+                securityContext);
+        runnable.setEnableOnOriginalThread(true);
+        assertWrapped(runnable);
     }
     }
 
 
     // --- create ---
     // --- create ---
@@ -112,19 +139,18 @@ public class DelegatingSecurityContextRunnableTests {
     }
     }
 
 
     @Test
     @Test
-    public void createNullSecurityContext() {
+    public void createNullSecurityContext() throws Exception {
         SecurityContextHolder.setContext(securityContext);
         SecurityContextHolder.setContext(securityContext);
         runnable = DelegatingSecurityContextRunnable.create(delegate, null);
         runnable = DelegatingSecurityContextRunnable.create(delegate, null);
-        SecurityContextHolder.clearContext(); // ensure runnable is what sets up the SecurityContextHolder
-        runnable.run();
-        assertWrapped();
+        SecurityContextHolder.clearContext(); // ensure runnable is what sets up the
+                                                // SecurityContextHolder
+        assertWrapped(runnable);
     }
     }
 
 
     @Test
     @Test
-    public void create() {
+    public void create() throws Exception {
         runnable = DelegatingSecurityContextRunnable.create(delegate, securityContext);
         runnable = DelegatingSecurityContextRunnable.create(delegate, securityContext);
-        runnable.run();
-        assertWrapped();
+        assertWrapped(runnable);
     }
     }
 
 
     // --- toString
     // --- toString
@@ -136,8 +162,15 @@ public class DelegatingSecurityContextRunnableTests {
         assertThat(runnable.toString()).isEqualTo(delegate.toString());
         assertThat(runnable.toString()).isEqualTo(delegate.toString());
     }
     }
 
 
-    private void assertWrapped() {
+    private void assertWrapped(Runnable runnable) throws Exception {
+        Future<?> submit = executor.submit(runnable);
+        submit.get();
         verify(delegate).run();
         verify(delegate).run();
-        assertThat(SecurityContextHolder.getContext()).isEqualTo(SecurityContextHolder.createEmptyContext());
+        assertThat(SecurityContextHolder.getContext()).isEqualTo(
+                SecurityContextHolder.createEmptyContext());
+    }
+
+    private static ExecutorService synchronousExecutor() {
+        return new ExecutorServiceAdapter(new SyncTaskExecutor());
     }
     }
 }
 }

+ 108 - 49
web/src/test/java/org/springframework/security/web/servletapi/SecurityContextHolderAwareRequestFilterTests.java

@@ -63,7 +63,6 @@ import org.springframework.security.web.AuthenticationEntryPoint;
 import org.springframework.security.web.authentication.logout.LogoutHandler;
 import org.springframework.security.web.authentication.logout.LogoutHandler;
 import org.springframework.util.ClassUtils;
 import org.springframework.util.ClassUtils;
 
 
-
 /**
 /**
  * Tests {@link SecurityContextHolderAwareRequestFilter}.
  * Tests {@link SecurityContextHolderAwareRequestFilter}.
  *
  *
@@ -107,18 +106,23 @@ public class SecurityContextHolderAwareRequestFilterTests {
         SecurityContextHolder.clearContext();
         SecurityContextHolder.clearContext();
     }
     }
 
 
-    //~ Methods ========================================================================================================
+    // ~ Methods
+    // ========================================================================================================
 
 
     @Test
     @Test
     public void expectedRequestWrapperClassIsUsed() throws Exception {
     public void expectedRequestWrapperClassIsUsed() throws Exception {
         filter.setRolePrefix("ROLE_");
         filter.setRolePrefix("ROLE_");
 
 
-        filter.doFilter(new MockHttpServletRequest(), new MockHttpServletResponse(), filterChain);
+        filter.doFilter(new MockHttpServletRequest(), new MockHttpServletResponse(),
+                filterChain);
 
 
         // Now re-execute the filter, ensuring our replacement wrapper is still used
         // Now re-execute the filter, ensuring our replacement wrapper is still used
-        filter.doFilter(new MockHttpServletRequest(), new MockHttpServletResponse(), filterChain);
+        filter.doFilter(new MockHttpServletRequest(), new MockHttpServletResponse(),
+                filterChain);
 
 
-        verify(filterChain, times(2)).doFilter(any(SecurityContextHolderAwareRequestWrapper.class), any(HttpServletResponse.class));
+        verify(filterChain, times(2)).doFilter(
+                any(SecurityContextHolderAwareRequestWrapper.class),
+                any(HttpServletResponse.class));
 
 
         filter.destroy();
         filter.destroy();
     }
     }
@@ -126,17 +130,20 @@ public class SecurityContextHolderAwareRequestFilterTests {
     @Test
     @Test
     public void authenticateFalse() throws Exception {
     public void authenticateFalse() throws Exception {
         assertThat(wrappedRequest().authenticate(response)).isFalse();
         assertThat(wrappedRequest().authenticate(response)).isFalse();
-        verify(authenticationEntryPoint).commence(eq(requestCaptor.getValue()), eq(response), any(AuthenticationException.class));
+        verify(authenticationEntryPoint).commence(eq(requestCaptor.getValue()),
+                eq(response), any(AuthenticationException.class));
         verifyZeroInteractions(authenticationManager, logoutHandler);
         verifyZeroInteractions(authenticationManager, logoutHandler);
         verify(request, times(0)).authenticate(any(HttpServletResponse.class));
         verify(request, times(0)).authenticate(any(HttpServletResponse.class));
     }
     }
 
 
     @Test
     @Test
     public void authenticateTrue() throws Exception {
     public void authenticateTrue() throws Exception {
-        SecurityContextHolder.getContext().setAuthentication(new TestingAuthenticationToken("test","password","ROLE_USER"));
+        SecurityContextHolder.getContext().setAuthentication(
+                new TestingAuthenticationToken("test", "password", "ROLE_USER"));
 
 
         assertThat(wrappedRequest().authenticate(response)).isTrue();
         assertThat(wrappedRequest().authenticate(response)).isTrue();
-        verifyZeroInteractions(authenticationEntryPoint, authenticationManager, logoutHandler);
+        verifyZeroInteractions(authenticationEntryPoint, authenticationManager,
+                logoutHandler);
         verify(request, times(0)).authenticate(any(HttpServletResponse.class));
         verify(request, times(0)).authenticate(any(HttpServletResponse.class));
     }
     }
 
 
@@ -147,7 +154,8 @@ public class SecurityContextHolderAwareRequestFilterTests {
 
 
         assertThat(wrappedRequest().authenticate(response)).isFalse();
         assertThat(wrappedRequest().authenticate(response)).isFalse();
         verify(request).authenticate(response);
         verify(request).authenticate(response);
-        verifyZeroInteractions(authenticationEntryPoint, authenticationManager, logoutHandler);
+        verifyZeroInteractions(authenticationEntryPoint, authenticationManager,
+                logoutHandler);
     }
     }
 
 
     @Test
     @Test
@@ -158,53 +166,73 @@ public class SecurityContextHolderAwareRequestFilterTests {
 
 
         assertThat(wrappedRequest().authenticate(response)).isTrue();
         assertThat(wrappedRequest().authenticate(response)).isTrue();
         verify(request).authenticate(response);
         verify(request).authenticate(response);
-        verifyZeroInteractions(authenticationEntryPoint, authenticationManager, logoutHandler);
+        verifyZeroInteractions(authenticationEntryPoint, authenticationManager,
+                logoutHandler);
     }
     }
 
 
     @Test
     @Test
     public void login() throws Exception {
     public void login() throws Exception {
-        TestingAuthenticationToken expectedAuth = new TestingAuthenticationToken("user", "password","ROLE_USER");
-        when(authenticationManager.authenticate(any(UsernamePasswordAuthenticationToken.class))).thenReturn(expectedAuth);
-
-        wrappedRequest().login(expectedAuth.getName(),String.valueOf(expectedAuth.getCredentials()));
-
-        assertThat(SecurityContextHolder.getContext().getAuthentication()).isSameAs(expectedAuth);
+        TestingAuthenticationToken expectedAuth = new TestingAuthenticationToken("user",
+                "password", "ROLE_USER");
+        when(
+                authenticationManager
+                        .authenticate(any(UsernamePasswordAuthenticationToken.class)))
+                .thenReturn(expectedAuth);
+
+        wrappedRequest().login(expectedAuth.getName(),
+                String.valueOf(expectedAuth.getCredentials()));
+
+        assertThat(SecurityContextHolder.getContext().getAuthentication()).isSameAs(
+                expectedAuth);
         verifyZeroInteractions(authenticationEntryPoint, logoutHandler);
         verifyZeroInteractions(authenticationEntryPoint, logoutHandler);
-        verify(request, times(0)).login(anyString(),anyString());
+        verify(request, times(0)).login(anyString(), anyString());
     }
     }
 
 
     // SEC-2296
     // SEC-2296
     @Test
     @Test
     public void loginWithExstingUser() throws Exception {
     public void loginWithExstingUser() throws Exception {
-        TestingAuthenticationToken expectedAuth = new TestingAuthenticationToken("user", "password","ROLE_USER");
-        when(authenticationManager.authenticate(any(UsernamePasswordAuthenticationToken.class))).thenReturn(new TestingAuthenticationToken("newuser","not be found","ROLE_USER"));
+        TestingAuthenticationToken expectedAuth = new TestingAuthenticationToken("user",
+                "password", "ROLE_USER");
+        when(
+                authenticationManager
+                        .authenticate(any(UsernamePasswordAuthenticationToken.class)))
+                .thenReturn(
+                        new TestingAuthenticationToken("newuser", "not be found",
+                                "ROLE_USER"));
         SecurityContextHolder.getContext().setAuthentication(expectedAuth);
         SecurityContextHolder.getContext().setAuthentication(expectedAuth);
 
 
         try {
         try {
-            wrappedRequest().login(expectedAuth.getName(),String.valueOf(expectedAuth.getCredentials()));
+            wrappedRequest().login(expectedAuth.getName(),
+                    String.valueOf(expectedAuth.getCredentials()));
             fail("Expected Exception");
             fail("Expected Exception");
-        } catch(ServletException success) {
-            assertThat(SecurityContextHolder.getContext().getAuthentication()).isSameAs(expectedAuth);
+        }
+        catch (ServletException success) {
+            assertThat(SecurityContextHolder.getContext().getAuthentication()).isSameAs(
+                    expectedAuth);
             verifyZeroInteractions(authenticationEntryPoint, logoutHandler);
             verifyZeroInteractions(authenticationEntryPoint, logoutHandler);
-            verify(request, times(0)).login(anyString(),anyString());
+            verify(request, times(0)).login(anyString(), anyString());
         }
         }
     }
     }
 
 
     @Test
     @Test
     public void loginFail() throws Exception {
     public void loginFail() throws Exception {
         AuthenticationException authException = new BadCredentialsException("Invalid");
         AuthenticationException authException = new BadCredentialsException("Invalid");
-        when(authenticationManager.authenticate(any(UsernamePasswordAuthenticationToken.class))).thenThrow(authException);
+        when(
+                authenticationManager
+                        .authenticate(any(UsernamePasswordAuthenticationToken.class)))
+                .thenThrow(authException);
 
 
         try {
         try {
-            wrappedRequest().login("invalid","credentials");
+            wrappedRequest().login("invalid", "credentials");
             Assert.fail("Expected Exception");
             Assert.fail("Expected Exception");
-        } catch(ServletException success) {
+        }
+        catch (ServletException success) {
             assertThat(success.getCause()).isEqualTo(authException);
             assertThat(success.getCause()).isEqualTo(authException);
         }
         }
         assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull();
         assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull();
 
 
         verifyZeroInteractions(authenticationEntryPoint, logoutHandler);
         verifyZeroInteractions(authenticationEntryPoint, logoutHandler);
-        verify(request, times(0)).login(anyString(),anyString());
+        verify(request, times(0)).login(anyString(), anyString());
     }
     }
 
 
     @Test
     @Test
@@ -218,7 +246,8 @@ public class SecurityContextHolderAwareRequestFilterTests {
         wrappedRequest().login(username, password);
         wrappedRequest().login(username, password);
 
 
         verify(request).login(username, password);
         verify(request).login(username, password);
-        verifyZeroInteractions(authenticationEntryPoint, authenticationManager, logoutHandler);
+        verifyZeroInteractions(authenticationEntryPoint, authenticationManager,
+                logoutHandler);
     }
     }
 
 
     @Test
     @Test
@@ -234,16 +263,19 @@ public class SecurityContextHolderAwareRequestFilterTests {
         try {
         try {
             wrappedRequest().login(username, password);
             wrappedRequest().login(username, password);
             Assert.fail("Expected Exception");
             Assert.fail("Expected Exception");
-        } catch(ServletException success) {
+        }
+        catch (ServletException success) {
             assertThat(success).isEqualTo(authException);
             assertThat(success).isEqualTo(authException);
         }
         }
 
 
-        verifyZeroInteractions(authenticationEntryPoint, authenticationManager, logoutHandler);
+        verifyZeroInteractions(authenticationEntryPoint, authenticationManager,
+                logoutHandler);
     }
     }
 
 
     @Test
     @Test
     public void logout() throws Exception {
     public void logout() throws Exception {
-        TestingAuthenticationToken expectedAuth = new TestingAuthenticationToken("user", "password","ROLE_USER");
+        TestingAuthenticationToken expectedAuth = new TestingAuthenticationToken("user",
+                "password", "ROLE_USER");
         SecurityContextHolder.getContext().setAuthentication(expectedAuth);
         SecurityContextHolder.getContext().setAuthentication(expectedAuth);
 
 
         HttpServletRequest wrappedRequest = wrappedRequest();
         HttpServletRequest wrappedRequest = wrappedRequest();
@@ -262,78 +294,105 @@ public class SecurityContextHolderAwareRequestFilterTests {
         wrappedRequest().logout();
         wrappedRequest().logout();
 
 
         verify(request).logout();
         verify(request).logout();
-        verifyZeroInteractions(authenticationEntryPoint, authenticationManager, logoutHandler);
+        verifyZeroInteractions(authenticationEntryPoint, authenticationManager,
+                logoutHandler);
     }
     }
 
 
     @Test
     @Test
     public void getAsyncContextStart() throws Exception {
     public void getAsyncContextStart() throws Exception {
         ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
         ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
         SecurityContext context = SecurityContextHolder.createEmptyContext();
         SecurityContext context = SecurityContextHolder.createEmptyContext();
-        TestingAuthenticationToken expectedAuth = new TestingAuthenticationToken("user", "password","ROLE_USER");
+        TestingAuthenticationToken expectedAuth = new TestingAuthenticationToken("user",
+                "password", "ROLE_USER");
         context.setAuthentication(expectedAuth);
         context.setAuthentication(expectedAuth);
         SecurityContextHolder.setContext(context);
         SecurityContextHolder.setContext(context);
         AsyncContext asyncContext = mock(AsyncContext.class);
         AsyncContext asyncContext = mock(AsyncContext.class);
         when(request.getAsyncContext()).thenReturn(asyncContext);
         when(request.getAsyncContext()).thenReturn(asyncContext);
         Runnable runnable = new Runnable() {
         Runnable runnable = new Runnable() {
-            public void run() {}
+            public void run() {
+            }
         };
         };
 
 
         wrappedRequest().getAsyncContext().start(runnable);
         wrappedRequest().getAsyncContext().start(runnable);
 
 
         verifyZeroInteractions(authenticationManager, logoutHandler);
         verifyZeroInteractions(authenticationManager, logoutHandler);
         verify(asyncContext).start(runnableCaptor.capture());
         verify(asyncContext).start(runnableCaptor.capture());
-        DelegatingSecurityContextRunnable wrappedRunnable = (DelegatingSecurityContextRunnable) runnableCaptor.getValue();
-        assertThat(WhiteboxImpl.getInternalState(wrappedRunnable, SecurityContext.class)).isEqualTo(context);
-        assertThat(WhiteboxImpl.getInternalState(wrappedRunnable, Runnable.class)).isEqualTo(runnable);
+        DelegatingSecurityContextRunnable wrappedRunnable = (DelegatingSecurityContextRunnable) runnableCaptor
+                .getValue();
+        assertThat(WhiteboxImpl.getInternalState(wrappedRunnable, SecurityContext.class))
+                .isEqualTo(context);
+        assertThat(WhiteboxImpl.getInternalState(wrappedRunnable, "delegate"))
+                .isEqualTo(runnable);
     }
     }
 
 
     @Test
     @Test
     public void startAsyncStart() throws Exception {
     public void startAsyncStart() throws Exception {
         ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
         ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
         SecurityContext context = SecurityContextHolder.createEmptyContext();
         SecurityContext context = SecurityContextHolder.createEmptyContext();
-        TestingAuthenticationToken expectedAuth = new TestingAuthenticationToken("user", "password","ROLE_USER");
+        TestingAuthenticationToken expectedAuth = new TestingAuthenticationToken("user",
+                "password", "ROLE_USER");
         context.setAuthentication(expectedAuth);
         context.setAuthentication(expectedAuth);
         SecurityContextHolder.setContext(context);
         SecurityContextHolder.setContext(context);
         AsyncContext asyncContext = mock(AsyncContext.class);
         AsyncContext asyncContext = mock(AsyncContext.class);
         when(request.startAsync()).thenReturn(asyncContext);
         when(request.startAsync()).thenReturn(asyncContext);
         Runnable runnable = new Runnable() {
         Runnable runnable = new Runnable() {
-            public void run() {}
+            public void run() {
+            }
         };
         };
 
 
         wrappedRequest().startAsync().start(runnable);
         wrappedRequest().startAsync().start(runnable);
 
 
         verifyZeroInteractions(authenticationManager, logoutHandler);
         verifyZeroInteractions(authenticationManager, logoutHandler);
         verify(asyncContext).start(runnableCaptor.capture());
         verify(asyncContext).start(runnableCaptor.capture());
-        DelegatingSecurityContextRunnable wrappedRunnable = (DelegatingSecurityContextRunnable) runnableCaptor.getValue();
-        assertThat(WhiteboxImpl.getInternalState(wrappedRunnable, SecurityContext.class)).isEqualTo(context);
-        assertThat(WhiteboxImpl.getInternalState(wrappedRunnable, Runnable.class)).isEqualTo(runnable);
+        DelegatingSecurityContextRunnable wrappedRunnable = (DelegatingSecurityContextRunnable) runnableCaptor
+                .getValue();
+        assertThat(WhiteboxImpl.getInternalState(wrappedRunnable, SecurityContext.class))
+                .isEqualTo(context);
+        assertThat(WhiteboxImpl.getInternalState(wrappedRunnable, "delegate"))
+                .isEqualTo(runnable);
     }
     }
 
 
     @Test
     @Test
     public void startAsyncWithRequestResponseStart() throws Exception {
     public void startAsyncWithRequestResponseStart() throws Exception {
         ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
         ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
         SecurityContext context = SecurityContextHolder.createEmptyContext();
         SecurityContext context = SecurityContextHolder.createEmptyContext();
-        TestingAuthenticationToken expectedAuth = new TestingAuthenticationToken("user", "password","ROLE_USER");
+        TestingAuthenticationToken expectedAuth = new TestingAuthenticationToken("user",
+                "password", "ROLE_USER");
         context.setAuthentication(expectedAuth);
         context.setAuthentication(expectedAuth);
         SecurityContextHolder.setContext(context);
         SecurityContextHolder.setContext(context);
         AsyncContext asyncContext = mock(AsyncContext.class);
         AsyncContext asyncContext = mock(AsyncContext.class);
-        when(request.startAsync(request,response)).thenReturn(asyncContext);
+        when(request.startAsync(request, response)).thenReturn(asyncContext);
         Runnable runnable = new Runnable() {
         Runnable runnable = new Runnable() {
-            public void run() {}
+            public void run() {
+            }
         };
         };
 
 
         wrappedRequest().startAsync(request, response).start(runnable);
         wrappedRequest().startAsync(request, response).start(runnable);
 
 
         verifyZeroInteractions(authenticationManager, logoutHandler);
         verifyZeroInteractions(authenticationManager, logoutHandler);
         verify(asyncContext).start(runnableCaptor.capture());
         verify(asyncContext).start(runnableCaptor.capture());
-        DelegatingSecurityContextRunnable wrappedRunnable = (DelegatingSecurityContextRunnable) runnableCaptor.getValue();
-        assertThat(WhiteboxImpl.getInternalState(wrappedRunnable, SecurityContext.class)).isEqualTo(context);
-        assertThat(WhiteboxImpl.getInternalState(wrappedRunnable, Runnable.class)).isEqualTo(runnable);
+        DelegatingSecurityContextRunnable wrappedRunnable = (DelegatingSecurityContextRunnable) runnableCaptor
+                .getValue();
+        assertThat(WhiteboxImpl.getInternalState(wrappedRunnable, SecurityContext.class))
+                .isEqualTo(context);
+        assertThat(WhiteboxImpl.getInternalState(wrappedRunnable, "delegate"))
+                .isEqualTo(runnable);
+    }
+
+    // SEC-3047
+    @Test
+    public void updateRequestFactory() throws Exception {
+        SecurityContextHolder.getContext().setAuthentication(new TestingAuthenticationToken("user",
+                "password", "PREFIX_USER"));
+        filter.setRolePrefix("PREFIX_");
+
+        assertThat(wrappedRequest().isUserInRole("PREFIX_USER")).isTrue();;
     }
     }
 
 
     private HttpServletRequest wrappedRequest() throws Exception {
     private HttpServletRequest wrappedRequest() throws Exception {
         filter.doFilter(request, response, filterChain);
         filter.doFilter(request, response, filterChain);
-        verify(filterChain).doFilter(requestCaptor.capture(), any(HttpServletResponse.class));
+        verify(filterChain).doFilter(requestCaptor.capture(),
+                any(HttpServletResponse.class));
 
 
         return requestCaptor.getValue();
         return requestCaptor.getValue();
     }
     }