فهرست منبع

SEC-2027: Invoke SecurityContextHolder.clearContext() only on outer invocation of FilterChainProxy

When SEC-1950 was introduced it caused problems when a <filter-mapping> was mapped
to multiple dispatchers (i.e. REQUEST and FORWARD) since when the second dispatcher
completed execution it cleared the SecurityContext and the original FilterChain
would then save the cleared out SecurityContext.

We now use a pattern similar to the OncePerRequestFilter to only invoke
SecurityContextHolder.clearContext() on the first invocation of the Filter. We do not simply extend
OncePerRequestFilter because we want to invoke the delegate filters for every request.
Rob Winch 13 سال پیش
والد
کامیت
ffe2834f4c

+ 12 - 4
web/src/main/java/org/springframework/security/web/FilterChainProxy.java

@@ -125,6 +125,8 @@ public class FilterChainProxy extends GenericFilterBean {
 
     //~ Instance fields ================================================================================================
 
+    private final static String FILTER_APPLIED = FilterChainProxy.class.getName().concat(".APPLIED");
+
     private List<SecurityFilterChain> filterChains;
 
     private FilterChainValidator filterChainValidator = new NullFilterChainValidator();
@@ -151,11 +153,17 @@ public class FilterChainProxy extends GenericFilterBean {
 
     public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
             throws IOException, ServletException {
-        try {
+        boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
+        if(clearContext) {
+            try {
+                request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
+                doFilterInternal(request, response, chain);
+            } finally {
+                SecurityContextHolder.clearContext();
+                request.removeAttribute(FILTER_APPLIED);
+            }
+        } else {
             doFilterInternal(request, response, chain);
-        } finally {
-            // SEC-1950
-            SecurityContextHolder.clearContext();
         }
     }
 

+ 27 - 0
web/src/test/java/org/springframework/security/web/FilterChainProxyTests.java

@@ -196,4 +196,31 @@ public class FilterChainProxyTests {
 
         assertNull(SecurityContextHolder.getContext().getAuthentication());
     }
+
+    // SEC-2027
+    @Test
+    public void doFilterClearsSecurityContextHolderOnceOnForwards() throws Exception {
+        final FilterChain innerChain = mock(FilterChain.class);
+        when(matcher.matches(any(HttpServletRequest.class))).thenReturn(true);
+        doAnswer(new Answer<Object>() {
+            public Object answer(InvocationOnMock inv) throws Throwable {
+                TestingAuthenticationToken expected = new TestingAuthenticationToken("username", "password");
+                SecurityContextHolder.getContext().setAuthentication(expected);
+                doAnswer(new Answer<Object>() {
+                    public Object answer(InvocationOnMock inv) throws Throwable {
+                        innerChain.doFilter(request, response);
+                        return null;
+                    }
+                }).when(filter).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class), any(FilterChain.class));;
+                fcp.doFilter(request, response, innerChain);
+                assertSame(expected, SecurityContextHolder.getContext().getAuthentication());
+                return null;
+            }
+        }).when(filter).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class), any(FilterChain.class));
+
+        fcp.doFilter(request, response, chain);
+
+        verify(innerChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
+        assertNull(SecurityContextHolder.getContext().getAuthentication());
+    }
 }