Extract a SecurityFilterChain interface and create a default implementation to facilitate other configuration options.
This commit is contained in:
		
							parent
							
								
									2d271666a4
								
							
						
					
					
						commit
						f92589f051
					
				|  | @ -9,6 +9,7 @@ import org.apache.commons.logging.LogFactory; | |||
| import org.springframework.security.access.AccessDeniedException; | ||||
| import org.springframework.security.access.ConfigAttribute; | ||||
| import org.springframework.security.authentication.AnonymousAuthenticationToken; | ||||
| import org.springframework.security.web.DefaultSecurityFilterChain; | ||||
| import org.springframework.security.web.FilterChainProxy; | ||||
| import org.springframework.security.web.FilterInvocation; | ||||
| import org.springframework.security.web.SecurityFilterChain; | ||||
|  | @ -24,6 +25,7 @@ import org.springframework.security.web.context.SecurityContextPersistenceFilter | |||
| import org.springframework.security.web.jaasapi.JaasApiIntegrationFilter; | ||||
| import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter; | ||||
| import org.springframework.security.web.session.SessionManagementFilter; | ||||
| import org.springframework.security.web.util.AnyRequestMatcher; | ||||
| 
 | ||||
| public class DefaultFilterChainValidator implements FilterChainProxy.FilterChainValidator { | ||||
|     private final Log logger = LogFactory.getLog(getClass()); | ||||
|  | @ -34,17 +36,34 @@ public class DefaultFilterChainValidator implements FilterChainProxy.FilterChain | |||
|             checkFilterStack(filterChain.getFilters()); | ||||
|         } | ||||
| 
 | ||||
|         checkPathOrder(new ArrayList<SecurityFilterChain>(fcp.getFilterChains())); | ||||
|         checkForDuplicateMatchers(new ArrayList<SecurityFilterChain>(fcp.getFilterChains())); | ||||
|     } | ||||
| 
 | ||||
|     private void checkForDuplicateMatchers(List<SecurityFilterChain> chains) { | ||||
|         SecurityFilterChain chain = chains.remove(0); | ||||
|     private void checkPathOrder(List<SecurityFilterChain> filterChains) { | ||||
|         // Check that the universal pattern is listed at the end, if at all | ||||
|         Iterator<SecurityFilterChain> chains = filterChains.iterator(); | ||||
| 
 | ||||
|         for (SecurityFilterChain test : chains) { | ||||
|             if (chain.getRequestMatcher().equals(test.getRequestMatcher())) { | ||||
|                 throw new IllegalArgumentException("The FilterChainProxy contains two filter chains using the" + | ||||
|                         " matcher " + chain.getRequestMatcher() + ". If you are using multiple <http> namespace " + | ||||
|                         "elements, you must use a 'pattern' attribute to define the request patterns to which they apply."); | ||||
|         while (chains.hasNext()) { | ||||
|             if (((DefaultSecurityFilterChain)chains.next()).getRequestMatcher() instanceof AnyRequestMatcher && chains.hasNext()) { | ||||
|                 throw new IllegalArgumentException("A universal match pattern ('/**') is defined " + | ||||
|                         " before other patterns in the filter chain, causing them to be ignored. Please check the " + | ||||
|                         "ordering in your <security:http> namespace or FilterChainProxy bean configuration"); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private void checkForDuplicateMatchers(List<SecurityFilterChain> chains) { | ||||
| 
 | ||||
|         while (chains.size() > 1) { | ||||
|             DefaultSecurityFilterChain chain = (DefaultSecurityFilterChain)chains.remove(0); | ||||
| 
 | ||||
|             for (SecurityFilterChain test : chains) { | ||||
|                 if (chain.getRequestMatcher().equals(((DefaultSecurityFilterChain)test).getRequestMatcher())) { | ||||
|                     throw new IllegalArgumentException("The FilterChainProxy contains two filter chains using the" + | ||||
|                             " matcher " + chain.getRequestMatcher() + ". If you are using multiple <http> namespace " + | ||||
|                             "elements, you must use a 'pattern' attribute to define the request patterns to which they apply."); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  |  | |||
|  | @ -7,6 +7,7 @@ import org.springframework.beans.factory.support.ManagedList; | |||
| import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser; | ||||
| import org.springframework.beans.factory.xml.BeanDefinitionParser; | ||||
| import org.springframework.beans.factory.xml.ParserContext; | ||||
| import org.springframework.security.web.DefaultSecurityFilterChain; | ||||
| import org.springframework.security.web.SecurityFilterChain; | ||||
| import org.springframework.util.Assert; | ||||
| import org.springframework.util.StringUtils; | ||||
|  | @ -26,7 +27,7 @@ public class FilterChainBeanDefinitionParser implements BeanDefinitionParser { | |||
|         String requestMatcher = elt.getAttribute(ATT_REQUEST_MATCHER_REF); | ||||
|         String filters = elt.getAttribute(HttpSecurityBeanDefinitionParser.ATT_FILTERS); | ||||
| 
 | ||||
|         BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(SecurityFilterChain.class); | ||||
|         BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(DefaultSecurityFilterChain.class); | ||||
| 
 | ||||
|         if (StringUtils.hasText(path)) { | ||||
|             Assert.isTrue(!StringUtils.hasText(requestMatcher), ""); | ||||
|  |  | |||
|  | @ -21,6 +21,7 @@ import org.springframework.security.authentication.ProviderManager; | |||
| import org.springframework.security.config.BeanIds; | ||||
| import org.springframework.security.config.Elements; | ||||
| import org.springframework.security.config.authentication.AuthenticationManagerFactoryBean; | ||||
| import org.springframework.security.web.DefaultSecurityFilterChain; | ||||
| import org.springframework.security.web.FilterChainProxy; | ||||
| import org.springframework.security.web.SecurityFilterChain; | ||||
| import org.springframework.security.web.util.AnyRequestMatcher; | ||||
|  | @ -147,7 +148,7 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser { | |||
|             filterChainMatcher = new RootBeanDefinition(AnyRequestMatcher.class); | ||||
|         } | ||||
| 
 | ||||
|         BeanDefinitionBuilder filterChainBldr = BeanDefinitionBuilder.rootBeanDefinition(SecurityFilterChain.class); | ||||
|         BeanDefinitionBuilder filterChainBldr = BeanDefinitionBuilder.rootBeanDefinition(DefaultSecurityFilterChain.class); | ||||
|         filterChainBldr.addConstructorArgValue(filterChainMatcher); | ||||
|         filterChainBldr.addConstructorArgValue(filterChain); | ||||
| 
 | ||||
|  |  | |||
|  | @ -42,7 +42,7 @@ class MultiHttpBlockConfigTests extends AbstractHttpConfigTests { | |||
|         createAppContext() | ||||
|         then: | ||||
|         BeanCreationException e = thrown() | ||||
|         e.cause.cause instanceof IllegalArgumentException | ||||
|         e.cause instanceof IllegalArgumentException | ||||
|     } | ||||
| 
 | ||||
|   def duplicatePatternsAreRejected () { | ||||
|  |  | |||
|  | @ -33,6 +33,7 @@ import org.springframework.beans.factory.BeanCreationException; | |||
| import org.springframework.context.support.ClassPathXmlApplicationContext; | ||||
| import org.springframework.mock.web.MockHttpServletRequest; | ||||
| import org.springframework.mock.web.MockHttpServletResponse; | ||||
| import org.springframework.security.web.DefaultSecurityFilterChain; | ||||
| import org.springframework.security.web.FilterChainProxy; | ||||
| import org.springframework.security.web.SecurityFilterChain; | ||||
| import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; | ||||
|  | @ -67,11 +68,6 @@ public class FilterChainProxyConfigTests { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Test(expected=BeanCreationException.class) | ||||
|     public void misplacedUniversalPathShouldBeDetected() throws Exception { | ||||
|         appCtx.getBean("newFilterChainProxyWrongPathOrder", FilterChainProxy.class); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void normalOperation() throws Exception { | ||||
|         FilterChainProxy filterChainProxy = appCtx.getBean("filterChain", FilterChainProxy.class); | ||||
|  | @ -111,9 +107,13 @@ public class FilterChainProxyConfigTests { | |||
|         FilterChainProxy fcp = appCtx.getBean("sec1235FilterChainProxy", FilterChainProxy.class); | ||||
| 
 | ||||
|         List<SecurityFilterChain> chains = fcp.getFilterChains(); | ||||
|         assertEquals("/login*", ((AntPathRequestMatcher)chains.get(0).getRequestMatcher()).getPattern()); | ||||
|         assertEquals("/logout", ((AntPathRequestMatcher)chains.get(1).getRequestMatcher()).getPattern()); | ||||
|         assertTrue(chains.get(2).getRequestMatcher() instanceof AnyRequestMatcher); | ||||
|         assertEquals("/login*", getPattern(chains.get(0))); | ||||
|         assertEquals("/logout", getPattern(chains.get(1))); | ||||
|         assertTrue(((DefaultSecurityFilterChain)chains.get(2)).getRequestMatcher() instanceof AnyRequestMatcher); | ||||
|     } | ||||
| 
 | ||||
|     private String getPattern(SecurityFilterChain chain) { | ||||
|         return ((AntPathRequestMatcher)((DefaultSecurityFilterChain)chain).getRequestMatcher()).getPattern(); | ||||
|     } | ||||
| 
 | ||||
|     private void checkPathAndFilterOrder(FilterChainProxy filterChainProxy) throws Exception { | ||||
|  |  | |||
|  | @ -98,8 +98,11 @@ | |||
|             <sec:filter-chain pattern="/some/other/path/**" filters="sif,mockFilter,mockFilter2"/> | ||||
|            </util:list> | ||||
|         </constructor-arg> | ||||
|         <property name="filterChainValidator" ref="fcv" /> | ||||
|     </bean> | ||||
| 
 | ||||
|     <bean id="fcv" class="org.springframework.security.config.http.DefaultFilterChainValidator" /> | ||||
| 
 | ||||
|     <bean id="newFilterChainProxyRegex" class="org.springframework.security.web.FilterChainProxy"> | ||||
|         <sec:filter-chain-map path-type="regex"> | ||||
|             <sec:filter-chain pattern="\A/foo/.*\Z" filters="mockFilter"/> | ||||
|  | @ -124,7 +127,7 @@ | |||
|     <bean id="newFilterChainProxyNonNamespace" class="org.springframework.security.web.FilterChainProxy"> | ||||
|         <constructor-arg> | ||||
|             <list> | ||||
|                 <bean class="org.springframework.security.web.SecurityFilterChain"> | ||||
|                 <bean class="org.springframework.security.web.DefaultSecurityFilterChain"> | ||||
|                     <constructor-arg ref="fooMatcher"/> | ||||
|                     <constructor-arg> | ||||
|                         <list> | ||||
|  | @ -132,7 +135,7 @@ | |||
|                         </list> | ||||
|                     </constructor-arg> | ||||
|                 </bean> | ||||
|                 <bean class="org.springframework.security.web.SecurityFilterChain"> | ||||
|                 <bean class="org.springframework.security.web.DefaultSecurityFilterChain"> | ||||
|                     <constructor-arg> | ||||
|                         <bean class="org.springframework.security.web.util.AntPathRequestMatcher"> | ||||
|                             <constructor-arg value="/some/other/path/**"/> | ||||
|  | @ -146,7 +149,7 @@ | |||
|                         </list> | ||||
|                     </constructor-arg> | ||||
|                 </bean> | ||||
|                 <bean class="org.springframework.security.web.SecurityFilterChain"> | ||||
|                 <bean class="org.springframework.security.web.DefaultSecurityFilterChain"> | ||||
|                     <constructor-arg> | ||||
|                         <bean class="org.springframework.security.web.util.AntPathRequestMatcher"> | ||||
|                             <constructor-arg value="/do/not/filter*"/> | ||||
|  | @ -156,7 +159,7 @@ | |||
|                         <list /> | ||||
|                     </constructor-arg> | ||||
|                 </bean> | ||||
|                 <bean class="org.springframework.security.web.SecurityFilterChain"> | ||||
|                 <bean class="org.springframework.security.web.DefaultSecurityFilterChain"> | ||||
|                     <constructor-arg> | ||||
|                         <bean class="org.springframework.security.web.util.AntPathRequestMatcher"> | ||||
|                             <constructor-arg value="/**"/> | ||||
|  |  | |||
|  | @ -0,0 +1,45 @@ | |||
| package org.springframework.security.web; | ||||
| 
 | ||||
| import org.springframework.security.web.util.RequestMatcher; | ||||
| 
 | ||||
| import javax.servlet.Filter; | ||||
| import javax.servlet.http.HttpServletRequest; | ||||
| import java.util.*; | ||||
| 
 | ||||
| /** | ||||
|  * Standard implementation of {@code SecurityFilterChain}. | ||||
|  * | ||||
|  * @author Luke Taylor | ||||
|  * | ||||
|  * @since 3.1 | ||||
|  */ | ||||
| public final class DefaultSecurityFilterChain implements SecurityFilterChain { | ||||
|     private final RequestMatcher requestMatcher; | ||||
|     private final List<Filter> filters; | ||||
| 
 | ||||
|     public DefaultSecurityFilterChain(RequestMatcher requestMatcher, Filter... filters) { | ||||
|         this(requestMatcher, Arrays.asList(filters)); | ||||
|     } | ||||
| 
 | ||||
|     public DefaultSecurityFilterChain(RequestMatcher requestMatcher, List<Filter> filters) { | ||||
|         this.requestMatcher = requestMatcher; | ||||
|         this.filters = new ArrayList<Filter>(filters); | ||||
|     } | ||||
| 
 | ||||
|     public RequestMatcher getRequestMatcher() { | ||||
|         return requestMatcher; | ||||
|     } | ||||
| 
 | ||||
|     public List<Filter> getFilters() { | ||||
|         return filters; | ||||
|     } | ||||
| 
 | ||||
|     public boolean matches(HttpServletRequest request) { | ||||
|         return requestMatcher.matches(request); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public String toString() { | ||||
|         return "[ " + requestMatcher + ", " + filters + "]"; | ||||
|     } | ||||
| } | ||||
|  | @ -142,7 +142,6 @@ public class FilterChainProxy extends GenericFilterBean { | |||
| 
 | ||||
|     public FilterChainProxy(List<SecurityFilterChain> filterChains) { | ||||
|         this.filterChains = filterChains; | ||||
|         checkPathOrder(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|  | @ -219,10 +218,8 @@ public class FilterChainProxy extends GenericFilterBean { | |||
|         filterChains = new ArrayList<SecurityFilterChain>(filterChainMap.size()); | ||||
| 
 | ||||
|         for (Map.Entry<RequestMatcher,List<Filter>> entry : filterChainMap.entrySet()) { | ||||
|             filterChains.add(new SecurityFilterChain(entry.getKey(), entry.getValue())); | ||||
|             filterChains.add(new DefaultSecurityFilterChain(entry.getKey(), entry.getValue())); | ||||
|         } | ||||
| 
 | ||||
|         checkPathOrder(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | @ -238,25 +235,12 @@ public class FilterChainProxy extends GenericFilterBean { | |||
|         LinkedHashMap<RequestMatcher, List<Filter>> map =  new LinkedHashMap<RequestMatcher, List<Filter>>(); | ||||
| 
 | ||||
|         for (SecurityFilterChain chain : filterChains) { | ||||
|             map.put(chain.getRequestMatcher(), chain.getFilters()); | ||||
|             map.put(((DefaultSecurityFilterChain)chain).getRequestMatcher(), chain.getFilters()); | ||||
|         } | ||||
| 
 | ||||
|         return map; | ||||
|     } | ||||
| 
 | ||||
|     private void checkPathOrder() { | ||||
|         // Check that the universal pattern is listed at the end, if at all | ||||
|         Iterator<SecurityFilterChain> chains = filterChains.iterator(); | ||||
| 
 | ||||
|         while(chains.hasNext()) { | ||||
|             if ((chains.next().getRequestMatcher() instanceof AnyRequestMatcher && chains.hasNext())) { | ||||
|                 throw new IllegalArgumentException("A universal match pattern ('/**') is defined " + | ||||
|                         " before other patterns in the filter chain, causing them to be ignored. Please check the " + | ||||
|                         "ordering in your <security:http> namespace or FilterChainProxy bean configuration"); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return the list of {@code SecurityFilterChain}s which will be matched against and | ||||
|      *         applied to incoming requests. | ||||
|  |  | |||
|  | @ -1,54 +1,23 @@ | |||
| package org.springframework.security.web; | ||||
| 
 | ||||
| import org.springframework.security.web.util.RequestMatcher; | ||||
| 
 | ||||
| import javax.servlet.Filter; | ||||
| import javax.servlet.FilterChain; | ||||
| import javax.servlet.FilterConfig; | ||||
| import javax.servlet.ServletException; | ||||
| import javax.servlet.ServletRequest; | ||||
| import javax.servlet.ServletResponse; | ||||
| import javax.servlet.http.HttpServletRequest; | ||||
| import java.io.IOException; | ||||
| import java.util.*; | ||||
| 
 | ||||
| /** | ||||
|  * Bean which defines a filter chain which is capable of being matched against an {@code HttpServletRequest}. | ||||
|  * Defines a filter chain which is capable of being matched against an {@code HttpServletRequest}. | ||||
|  * in order to decide whether it applies to that request. | ||||
|  * <p> | ||||
|  * Used to configure a {@code FilterChainProxy}. | ||||
|  * | ||||
|  * | ||||
|  * @author Luke Taylor | ||||
|  * | ||||
|  * @since 3.1 | ||||
|  */ | ||||
| public final class SecurityFilterChain { | ||||
|     private final RequestMatcher requestMatcher; | ||||
|     private final List<Filter> filters; | ||||
| public interface SecurityFilterChain { | ||||
| 
 | ||||
|     public SecurityFilterChain(RequestMatcher requestMatcher, Filter... filters) { | ||||
|         this(requestMatcher, Arrays.asList(filters)); | ||||
|     } | ||||
|     boolean matches(HttpServletRequest request); | ||||
| 
 | ||||
|     public SecurityFilterChain(RequestMatcher requestMatcher, List<Filter> filters) { | ||||
|         this.requestMatcher = requestMatcher; | ||||
|         this.filters = new ArrayList<Filter>(filters); | ||||
|     } | ||||
| 
 | ||||
|     public RequestMatcher getRequestMatcher() { | ||||
|         return requestMatcher; | ||||
|     } | ||||
| 
 | ||||
|     public List<Filter> getFilters() { | ||||
|         return filters; | ||||
|     } | ||||
| 
 | ||||
|     public boolean matches(HttpServletRequest request) { | ||||
|         return requestMatcher.matches(request); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public String toString() { | ||||
|         return "[ " + requestMatcher + ", " + filters + "]"; | ||||
|     } | ||||
|     List<Filter> getFilters(); | ||||
| } | ||||
|  |  | |||
|  | @ -47,7 +47,7 @@ public class FilterChainProxyTests { | |||
|                         return null; | ||||
|                     } | ||||
|                 }).when(filter).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class), any(FilterChain.class)); | ||||
|         fcp = new FilterChainProxy(new SecurityFilterChain(matcher, Arrays.asList(filter))); | ||||
|         fcp = new FilterChainProxy(new DefaultSecurityFilterChain(matcher, Arrays.asList(filter))); | ||||
|         fcp.setFilterChainValidator(mock(FilterChainProxy.FilterChainValidator.class)); | ||||
|         request = new MockHttpServletRequest(); | ||||
|         request.setServletPath("/path"); | ||||
|  | @ -94,7 +94,7 @@ public class FilterChainProxyTests { | |||
|     @Test | ||||
|     public void originalFilterChainIsInvokedIfMatchingSecurityChainIsEmpty() throws Exception { | ||||
|         List<Filter> noFilters = Collections.emptyList(); | ||||
|         fcp = new FilterChainProxy(new SecurityFilterChain(matcher, noFilters)); | ||||
|         fcp = new FilterChainProxy(new DefaultSecurityFilterChain(matcher, noFilters)); | ||||
| 
 | ||||
|         when(matcher.matches(any(HttpServletRequest.class))).thenReturn(true); | ||||
|         fcp.doFilter(request, response, chain); | ||||
|  | @ -137,7 +137,7 @@ public class FilterChainProxyTests { | |||
|     @Test | ||||
|     public void bothWrappersAreResetWithNestedFcps() throws Exception { | ||||
|         HttpFirewall fw = mock(HttpFirewall.class); | ||||
|         FilterChainProxy firstFcp = new FilterChainProxy(new SecurityFilterChain(matcher, fcp)); | ||||
|         FilterChainProxy firstFcp = new FilterChainProxy(new DefaultSecurityFilterChain(matcher, fcp)); | ||||
|         firstFcp.setFirewall(fw); | ||||
|         fcp.setFirewall(fw); | ||||
|         FirewalledRequest firstFwr = mock(FirewalledRequest.class, "firstFwr"); | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue