diff --git a/config/src/main/java/org/springframework/security/config/BeanIds.java b/config/src/main/java/org/springframework/security/config/BeanIds.java index 738b6fe6c5..077904a2cf 100644 --- a/config/src/main/java/org/springframework/security/config/BeanIds.java +++ b/config/src/main/java/org/springframework/security/config/BeanIds.java @@ -25,6 +25,7 @@ public abstract class BeanIds { public static final String METHOD_ACCESS_MANAGER = PREFIX + "defaultMethodAccessManager"; public static final String FILTER_CHAIN_PROXY = PREFIX + "filterChainProxy"; + public static final String FILTER_CHAINS = PREFIX + "filterChains"; public static final String METHOD_SECURITY_METADATA_SOURCE_ADVISOR = PREFIX + "methodSecurityMetadataSourceAdvisor"; public static final String EMBEDDED_APACHE_DS = PREFIX + "apacheDirectoryServerContainer"; diff --git a/config/src/main/java/org/springframework/security/config/SecurityNamespaceHandler.java b/config/src/main/java/org/springframework/security/config/SecurityNamespaceHandler.java index 192cf61f22..05d97a7e62 100644 --- a/config/src/main/java/org/springframework/security/config/SecurityNamespaceHandler.java +++ b/config/src/main/java/org/springframework/security/config/SecurityNamespaceHandler.java @@ -1,8 +1,5 @@ package org.springframework.security.config; -import java.util.HashMap; -import java.util.Map; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.config.BeanDefinition; @@ -15,6 +12,7 @@ import org.springframework.security.config.authentication.AuthenticationManagerB import org.springframework.security.config.authentication.AuthenticationProviderBeanDefinitionParser; import org.springframework.security.config.authentication.JdbcUserServiceBeanDefinitionParser; import org.springframework.security.config.authentication.UserServiceBeanDefinitionParser; +import org.springframework.security.config.http.FilterChainBeanDefinitionParser; import org.springframework.security.config.http.FilterChainMapBeanDefinitionDecorator; import org.springframework.security.config.http.FilterInvocationSecurityMetadataSourceParser; import org.springframework.security.config.http.HttpFirewallBeanDefinitionParser; @@ -30,6 +28,8 @@ import org.springframework.util.ClassUtils; import org.w3c.dom.Element; import org.w3c.dom.Node; +import java.util.*; + /** * Parses elements from the "security" namespace (http://www.springframework.org/schema/security). * @@ -76,7 +76,7 @@ public final class SecurityNamespaceHandler implements NamespaceHandler { if (parser == null) { if (Elements.HTTP.equals(name) || Elements.FILTER_SECURITY_METADATA_SOURCE.equals(name) || - Elements.FILTER_CHAIN_MAP.equals(name)) { + Elements.FILTER_CHAIN_MAP.equals(name) || Elements.FILTER_CHAIN.equals(name)) { reportMissingWebClasses(name, pc, element); } else { reportUnsupportedNodeType(name, pc, element); @@ -147,6 +147,7 @@ public final class SecurityNamespaceHandler implements NamespaceHandler { parsers.put(Elements.HTTP_FIREWALL, new HttpFirewallBeanDefinitionParser()); parsers.put(Elements.FILTER_INVOCATION_DEFINITION_SOURCE, new FilterInvocationSecurityMetadataSourceParser()); parsers.put(Elements.FILTER_SECURITY_METADATA_SOURCE, new FilterInvocationSecurityMetadataSourceParser()); + parsers.put(Elements.FILTER_CHAIN, new FilterChainBeanDefinitionParser()); filterChainMapBDD = new FilterChainMapBeanDefinitionDecorator(); } } diff --git a/config/src/main/java/org/springframework/security/config/debug/DebugFilter.java b/config/src/main/java/org/springframework/security/config/debug/DebugFilter.java index d5a17cbdd4..57ef8c75a7 100644 --- a/config/src/main/java/org/springframework/security/config/debug/DebugFilter.java +++ b/config/src/main/java/org/springframework/security/config/debug/DebugFilter.java @@ -1,7 +1,7 @@ package org.springframework.security.config.debug; import org.springframework.security.web.FilterChainProxy; -import org.springframework.security.web.util.RequestMatcher; +import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.util.UrlUtils; import org.springframework.web.filter.OncePerRequestFilter; @@ -13,8 +13,7 @@ import javax.servlet.http.HttpServletRequestWrapper; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; -import java.util.List; -import java.util.Map; +import java.util.*; /** * Spring Security debugging filter. @@ -28,12 +27,10 @@ import java.util.Map; */ class DebugFilter extends OncePerRequestFilter { private final FilterChainProxy fcp; - private final Map> filterChainMap; private final Logger logger = new Logger(); public DebugFilter(FilterChainProxy fcp) { this.fcp = fcp; - this.filterChainMap = fcp.getFilterChainMap(); } @Override @@ -67,11 +64,9 @@ class DebugFilter extends OncePerRequestFilter { } private List getFilters(HttpServletRequest request) { - for (Map.Entry> entry : filterChainMap.entrySet()) { - RequestMatcher matcher = entry.getKey(); - - if (matcher.matches(request)) { - return entry.getValue(); + for (SecurityFilterChain chain : fcp.getFilterChains()) { + if (chain.matches(request)) { + return chain.getFilters(); } } diff --git a/config/src/main/java/org/springframework/security/config/http/DefaultFilterChainValidator.java b/config/src/main/java/org/springframework/security/config/http/DefaultFilterChainValidator.java index 6f06695cba..2ccac62534 100644 --- a/config/src/main/java/org/springframework/security/config/http/DefaultFilterChainValidator.java +++ b/config/src/main/java/org/springframework/security/config/http/DefaultFilterChainValidator.java @@ -1,7 +1,6 @@ package org.springframework.security.config.http; -import java.util.Collection; -import java.util.List; +import java.util.*; import javax.servlet.Filter; @@ -12,6 +11,7 @@ import org.springframework.security.access.ConfigAttribute; import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.web.FilterChainProxy; import org.springframework.security.web.FilterInvocation; +import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.access.ExceptionTranslationFilter; import org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource; import org.springframework.security.web.access.intercept.FilterSecurityInterceptor; @@ -29,9 +29,23 @@ public class DefaultFilterChainValidator implements FilterChainProxy.FilterChain private final Log logger = LogFactory.getLog(getClass()); public void validate(FilterChainProxy fcp) { - for(List filters : fcp.getFilterChainMap().values()) { - checkLoginPageIsntProtected(fcp, filters); - checkFilterStack(filters); + for(SecurityFilterChain filterChain : fcp.getFilterChains()) { + checkLoginPageIsntProtected(fcp, filterChain.getFilters()); + checkFilterStack(filterChain.getFilters()); + } + + checkForDuplicateMatchers(new ArrayList(fcp.getFilterChains())); + } + + private void checkForDuplicateMatchers(List chains) { + SecurityFilterChain chain = chains.remove(0); + + 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 namespace " + + "elements, you must use a 'pattern' attribute to define the request patterns to which they apply."); + } } } diff --git a/config/src/main/java/org/springframework/security/config/http/FilterChainBeanDefinitionParser.java b/config/src/main/java/org/springframework/security/config/http/FilterChainBeanDefinitionParser.java new file mode 100644 index 0000000000..b3b43320f5 --- /dev/null +++ b/config/src/main/java/org/springframework/security/config/http/FilterChainBeanDefinitionParser.java @@ -0,0 +1,45 @@ +package org.springframework.security.config.http; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.RuntimeBeanReference; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.ManagedList; +import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.util.StringUtils; +import org.w3c.dom.Element; + +import java.util.*; + +/** + * @author Luke Taylor + */ +public class FilterChainBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { + + @Override + protected Class getBeanClass(Element element) { + return SecurityFilterChain.class; + } + + @Override + protected void doParse(Element elt, BeanDefinitionBuilder builder) { + MatcherType matcherType = MatcherType.fromElement(elt); + String path = elt.getAttribute(HttpSecurityBeanDefinitionParser.ATT_PATH_PATTERN); + String filters = elt.getAttribute(HttpSecurityBeanDefinitionParser.ATT_FILTERS); + + builder.addConstructorArgValue(matcherType.createMatcher(path, null)); + + if (filters.equals(HttpSecurityBeanDefinitionParser.OPT_FILTERS_NONE)) { + builder.addConstructorArgValue(Collections.EMPTY_LIST); + } else { + String[] filterBeanNames = StringUtils.tokenizeToStringArray(filters, ","); + ManagedList filterChain = new ManagedList(filterBeanNames.length); + + for (String name : filterBeanNames) { + filterChain.add(new RuntimeBeanReference(name)); + } + + builder.addConstructorArgValue(filterChain); + } + } +} diff --git a/config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java b/config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java index 40f1aff8e1..74d9f189e1 100644 --- a/config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java +++ b/config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java @@ -95,13 +95,13 @@ class HttpConfigurationBuilder { private BeanReference fsi; private BeanReference requestCache; - public HttpConfigurationBuilder(Element element, ParserContext pc, MatcherType matcherType, + public HttpConfigurationBuilder(Element element, ParserContext pc, String portMapperName, BeanReference authenticationManager) { this.httpElt = element; this.pc = pc; this.portMapperName = portMapperName; - this.matcherType = matcherType; + this.matcherType = MatcherType.fromElement(element); interceptUrls = DomUtils.getChildElementsByTagName(element, Elements.INTERCEPT_URL); for (Element urlElt : interceptUrls) { @@ -339,7 +339,7 @@ class HttpConfigurationBuilder { servApiFilter = new RootBeanDefinition(SecurityContextHolderAwareRequestFilter.class); } } - + // Adds the jaas-api integration filter if required private void createJaasApiFilter() { final String ATT_JAAS_API_PROVISION = "jaas-api-provision"; @@ -354,7 +354,7 @@ class HttpConfigurationBuilder { jaasApiFilter = new RootBeanDefinition(JaasApiIntegrationFilter.class); } } - + private void createChannelProcessingFilter() { ManagedMap channelRequestMap = parseInterceptUrlsForChannelSecurity(); @@ -534,7 +534,7 @@ class HttpConfigurationBuilder { if (jaasApiFilter != null) { filters.add(new OrderDecorator(jaasApiFilter, JAAS_API_SUPPORT_FILTER)); } - + if (sfpf != null) { filters.add(new OrderDecorator(sfpf, SESSION_MANAGEMENT_FILTER)); } diff --git a/config/src/main/java/org/springframework/security/config/http/HttpFirewallBeanDefinitionParser.java b/config/src/main/java/org/springframework/security/config/http/HttpFirewallBeanDefinitionParser.java index 40c2559fc8..3b232ef51d 100644 --- a/config/src/main/java/org/springframework/security/config/http/HttpFirewallBeanDefinitionParser.java +++ b/config/src/main/java/org/springframework/security/config/http/HttpFirewallBeanDefinitionParser.java @@ -1,18 +1,13 @@ package org.springframework.security.config.http; -import org.springframework.beans.BeanMetadataElement; import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.config.BeanReference; import org.springframework.beans.factory.config.RuntimeBeanReference; -import org.springframework.beans.factory.support.ManagedMap; import org.springframework.beans.factory.xml.BeanDefinitionParser; import org.springframework.beans.factory.xml.ParserContext; import org.springframework.security.config.BeanIds; import org.springframework.util.StringUtils; import org.w3c.dom.Element; -import java.util.*; - /** * Injects the supplied {@code HttpFirewall} bean reference into the {@code FilterChainProxy}. * @@ -28,9 +23,7 @@ public class HttpFirewallBeanDefinitionParser implements BeanDefinitionParser { } // Ensure the FCP is registered. - HttpSecurityBeanDefinitionParser.registerFilterChainProxy(pc, - new ManagedMap(), - pc.extractSource(element)); + HttpSecurityBeanDefinitionParser.registerFilterChainProxyIfNecessary(pc, pc.extractSource(element)); BeanDefinition filterChainProxy = pc.getRegistry().getBeanDefinition(BeanIds.FILTER_CHAIN_PROXY); filterChainProxy.getPropertyValues().addPropertyValue("firewall", new RuntimeBeanReference(ref)); diff --git a/config/src/main/java/org/springframework/security/config/http/HttpSecurityBeanDefinitionParser.java b/config/src/main/java/org/springframework/security/config/http/HttpSecurityBeanDefinitionParser.java index b4506c5b0f..dd3dc6f27b 100644 --- a/config/src/main/java/org/springframework/security/config/http/HttpSecurityBeanDefinitionParser.java +++ b/config/src/main/java/org/springframework/security/config/http/HttpSecurityBeanDefinitionParser.java @@ -1,10 +1,5 @@ package org.springframework.security.config.http; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeanMetadataElement; @@ -13,12 +8,10 @@ import org.springframework.beans.factory.config.BeanReference; import org.springframework.beans.factory.config.ListFactoryBean; import org.springframework.beans.factory.config.MethodInvokingFactoryBean; import org.springframework.beans.factory.config.RuntimeBeanReference; -import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder; import org.springframework.beans.factory.parsing.BeanComponentDefinition; import org.springframework.beans.factory.parsing.CompositeComponentDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.ManagedList; -import org.springframework.beans.factory.support.ManagedMap; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.xml.BeanDefinitionParser; import org.springframework.beans.factory.xml.ParserContext; @@ -29,11 +22,14 @@ import org.springframework.security.config.BeanIds; import org.springframework.security.config.Elements; import org.springframework.security.config.authentication.AuthenticationManagerFactoryBean; import org.springframework.security.web.FilterChainProxy; +import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.util.AnyRequestMatcher; import org.springframework.util.StringUtils; import org.springframework.util.xml.DomUtils; import org.w3c.dom.Element; +import java.util.*; + /** * Sets up HTTP security: filter stack and protected URLs. * @@ -67,33 +63,29 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser { * By the end of this method, the default FilterChainProxy bean should have been registered and will have * the map of filter chains defined, with the "universal" match pattern mapped to the list of beans which have been parsed here. */ + @SuppressWarnings({"unchecked"}) public BeanDefinition parse(Element element, ParserContext pc) { CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), pc.extractSource(element)); pc.pushContainingComponent(compositeDef); - MatcherType matcherType = MatcherType.fromElement(element); - ManagedMap filterChainMap = new ManagedMap(); + registerFilterChainProxyIfNecessary(pc, pc.extractSource(element)); - String filterChainPattern = element.getAttribute(ATT_PATH_PATTERN); + // Obtain the filter chains and add the new chain to it + BeanDefinition listFactoryBean = pc.getRegistry().getBeanDefinition(BeanIds.FILTER_CHAINS); + List filterChains = (List) + listFactoryBean.getPropertyValues().getPropertyValue("sourceList").getValue(); - BeanDefinition filterChainMatcher; - - if (StringUtils.hasText(filterChainPattern)) { - filterChainMatcher = matcherType.createMatcher(filterChainPattern, null); - } else { - filterChainMatcher = new RootBeanDefinition(AnyRequestMatcher.class); - } - - filterChainMap.put(filterChainMatcher, createFilterChain(element, pc, matcherType)); - - registerFilterChainProxy(pc, filterChainMap, pc.extractSource(element)); + filterChains.add(createFilterChain(element, pc)); pc.popAndRegisterContainingComponent(); return null; } - BeanReference createFilterChain(Element element, ParserContext pc, MatcherType matcherType) { + /** + * Creates the {@code SecurityFilterChain} bean from an <http> element. + */ + private BeanReference createFilterChain(Element element, ParserContext pc) { boolean secured = !OPT_SECURITY_NONE.equals(element.getAttribute(ATT_SECURED)); if (!secured) { @@ -109,7 +101,7 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser { } } - return createFilterListBean(element, pc, Collections.emptyList()); + return createSecurityFilterChainBean(element, pc, Collections.emptyList()); } final String portMapperName = createPortMapper(element, pc); @@ -117,7 +109,7 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser { ManagedList authenticationProviders = new ManagedList(); BeanReference authenticationManager = createAuthenticationManager(element, pc, authenticationProviders); - HttpConfigurationBuilder httpBldr = new HttpConfigurationBuilder(element, pc, matcherType, + HttpConfigurationBuilder httpBldr = new HttpConfigurationBuilder(element, pc, portMapperName, authenticationManager); AuthenticationConfigBuilder authBldr = new AuthenticationConfigBuilder(element, pc, @@ -135,27 +127,41 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser { Collections.sort(unorderedFilterChain, new OrderComparator()); checkFilterChainOrder(unorderedFilterChain, pc, pc.extractSource(element)); + // The list of filter beans List filterChain = new ManagedList(); for (OrderDecorator od : unorderedFilterChain) { filterChain.add(od.bean); } - return createFilterListBean(element, pc, filterChain); + return createSecurityFilterChainBean(element, pc, filterChain); } - private BeanReference createFilterListBean(Element element, ParserContext pc, List filterChain) { - BeanDefinition listFactoryBean = new RootBeanDefinition(ListFactoryBean.class); + private BeanReference createSecurityFilterChainBean(Element element, ParserContext pc, List filterChain) { + BeanDefinition filterChainMatcher; + + String filterChainPattern = element.getAttribute(ATT_PATH_PATTERN); + if (StringUtils.hasText(filterChainPattern)) { + filterChainMatcher = MatcherType.fromElement(element).createMatcher(filterChainPattern, null); + } else { + filterChainMatcher = new RootBeanDefinition(AnyRequestMatcher.class); + } + + BeanDefinitionBuilder filterChainBldr = BeanDefinitionBuilder.rootBeanDefinition(SecurityFilterChain.class); + filterChainBldr.addConstructorArgValue(filterChainMatcher); + filterChainBldr.addConstructorArgValue(filterChain); + + BeanDefinition filterChainBean = filterChainBldr.getBeanDefinition(); String id = element.getAttribute("name"); if (!StringUtils.hasText(id)) { id = element.getAttribute("id"); if (!StringUtils.hasText(id)) { - id = pc.getReaderContext().generateBeanName(listFactoryBean); + id = pc.getReaderContext().generateBeanName(filterChainBean); } } - listFactoryBean.getPropertyValues().add("sourceList", filterChain); - pc.registerBeanComponent(new BeanComponentDefinition(listFactoryBean, id)); + + pc.registerBeanComponent(new BeanComponentDefinition(filterChainBean, id)); return new RuntimeBeanReference(id); } @@ -262,35 +268,22 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser { return customFilters; } - @SuppressWarnings("unchecked") - static void registerFilterChainProxy(ParserContext pc, Map filterChainMap, Object source) { + static void registerFilterChainProxyIfNecessary(ParserContext pc, Object source) { if (pc.getRegistry().containsBeanDefinition(BeanIds.FILTER_CHAIN_PROXY)) { - // Already registered. Obtain the filter chain map and add the new entries to it - - BeanDefinition fcp = pc.getRegistry().getBeanDefinition(BeanIds.FILTER_CHAIN_PROXY); - Map existingFilterChainMap = (Map) fcp.getPropertyValues().getPropertyValue("filterChainMap").getValue(); - - for (BeanDefinition matcherBean : filterChainMap.keySet()) { - if (existingFilterChainMap.containsKey(matcherBean)) { - Map args = matcherBean.getConstructorArgumentValues().getIndexedArgumentValues(); - String matcherError = args.size() == 2 ? args.get(0).getValue() + ", " +args.get(1).getValue() : - matcherBean.toString(); - pc.getReaderContext().error("The filter chain map already contains this request matcher [" - + matcherError + "]. If you are using multiple namespace elements, you must use a 'pattern' attribute" + - " to define the request patterns to which they apply.", source); - } - } - existingFilterChainMap.putAll(filterChainMap); - } else { - // Not already registered, so register it - BeanDefinitionBuilder fcpBldr = BeanDefinitionBuilder.rootBeanDefinition(FilterChainProxy.class); - fcpBldr.getRawBeanDefinition().setSource(source); - fcpBldr.addPropertyValue("filterChainMap", filterChainMap); - fcpBldr.addPropertyValue("filterChainValidator", new RootBeanDefinition(DefaultFilterChainValidator.class)); - BeanDefinition fcpBean = fcpBldr.getBeanDefinition(); - pc.registerBeanComponent(new BeanComponentDefinition(fcpBean, BeanIds.FILTER_CHAIN_PROXY)); - pc.getRegistry().registerAlias(BeanIds.FILTER_CHAIN_PROXY, BeanIds.SPRING_SECURITY_FILTER_CHAIN); + return; } + // Not already registered, so register the list of filter chains and the FilterChainProxy + BeanDefinition listFactoryBean = new RootBeanDefinition(ListFactoryBean.class); + listFactoryBean.getPropertyValues().add("sourceList", new ManagedList()); + pc.registerBeanComponent(new BeanComponentDefinition(listFactoryBean, BeanIds.FILTER_CHAINS)); + + BeanDefinitionBuilder fcpBldr = BeanDefinitionBuilder.rootBeanDefinition(FilterChainProxy.class); + fcpBldr.getRawBeanDefinition().setSource(source); + fcpBldr.addConstructorArgReference(BeanIds.FILTER_CHAINS); + fcpBldr.addPropertyValue("filterChainValidator", new RootBeanDefinition(DefaultFilterChainValidator.class)); + BeanDefinition fcpBean = fcpBldr.getBeanDefinition(); + pc.registerBeanComponent(new BeanComponentDefinition(fcpBean, BeanIds.FILTER_CHAIN_PROXY)); + pc.getRegistry().registerAlias(BeanIds.FILTER_CHAIN_PROXY, BeanIds.SPRING_SECURITY_FILTER_CHAIN); } } diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-3.1.rnc b/config/src/main/resources/org/springframework/security/config/spring-security-3.1.rnc index 2a923aaf45..cedf2a0eb4 100644 --- a/config/src/main/resources/org/springframework/security/config/spring-security-3.1.rnc +++ b/config/src/main/resources/org/springframework/security/config/spring-security-3.1.rnc @@ -443,7 +443,7 @@ filter-chain-map.attlist &= request-matcher? filter-chain = - ## Used within filter-chain-map to define a specific URL pattern and the list of filters which apply to the URLs matching that pattern. When multiple filter-chain elements are used within a filter-chain-map element, the most specific patterns must be placed at the top of the list, with most general ones at the bottom. + ## Used within to define a specific URL pattern and the list of filters which apply to the URLs matching that pattern. When multiple filter-chain elements are assembled in a list in order to configure a FilterChainProxy, the most specific patterns must be placed at the top of the list, with most general ones at the bottom. element filter-chain {filter-chain.attlist, empty} filter-chain.attlist &= attribute pattern {xsd:token} diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-3.1.xsd b/config/src/main/resources/org/springframework/security/config/spring-security-3.1.xsd index 0f36bad666..c66ab98bc9 100644 --- a/config/src/main/resources/org/springframework/security/config/spring-security-3.1.xsd +++ b/config/src/main/resources/org/springframework/security/config/spring-security-3.1.xsd @@ -974,11 +974,7 @@ Used to explicitly configure a FilterChainProxy instance with a FilterChainMap - - Used within filter-chain-map to define a specific URL pattern and the list of filters which apply to the URLs matching that pattern. When multiple filter-chain elements are used within a filter-chain-map element, the most specific patterns must be placed at the top of the list, with most general ones at the bottom. - - - + @@ -1004,7 +1000,11 @@ - + + Used within to define a specific URL pattern and the list of filters which apply to the URLs matching that pattern. When multiple filter-chain elements are assembled in a list in order to configure a FilterChainProxy, the most specific patterns must be placed at the top of the list, with most general ones at the bottom. + + + diff --git a/config/src/main/resources/org/springframework/security/config/spring-security.xsl b/config/src/main/resources/org/springframework/security/config/spring-security.xsl index 7f8922d88c..fe34d23d6e 100644 --- a/config/src/main/resources/org/springframework/security/config/spring-security.xsl +++ b/config/src/main/resources/org/springframework/security/config/spring-security.xsl @@ -9,7 +9,7 @@ - ,access-denied-handler,anonymous,session-management,concurrency-control,after-invocation-provider,authentication-provider,ldap-authentication-provider,user,port-mapping,openid-login,expression-handler,filter-chain,form-login,http-basic,intercept-url,logout,password-encoder,port-mappings,port-mapper,password-compare,protect,protect-pointcut,pre-post-annotation-handling,pre-invocation-advice,post-invocation-advice,invocation-attribute-factory,remember-me,salt-source,x509, + ,access-denied-handler,anonymous,session-management,concurrency-control,after-invocation-provider,authentication-provider,ldap-authentication-provider,user,port-mapping,openid-login,expression-handler,form-login,http-basic,intercept-url,logout,password-encoder,port-mappings,port-mapper,password-compare,protect,protect-pointcut,pre-post-annotation-handling,pre-invocation-advice,post-invocation-advice,invocation-attribute-factory,remember-me,salt-source,x509, diff --git a/config/src/test/groovy/org/springframework/security/config/http/MultiHttpBlockConfigTests.groovy b/config/src/test/groovy/org/springframework/security/config/http/MultiHttpBlockConfigTests.groovy index 3ce549797d..ca1fed2c78 100644 --- a/config/src/test/groovy/org/springframework/security/config/http/MultiHttpBlockConfigTests.groovy +++ b/config/src/test/groovy/org/springframework/security/config/http/MultiHttpBlockConfigTests.groovy @@ -4,6 +4,8 @@ import org.springframework.beans.factory.parsing.BeanDefinitionParsingException import org.springframework.security.config.BeanIds import org.springframework.security.web.FilterChainProxy import org.junit.Assert +import org.springframework.beans.factory.BeanCreationException +import org.springframework.security.web.SecurityFilterChain /** * Tests scenarios with multiple <http> elements. @@ -22,11 +24,11 @@ class MultiHttpBlockConfigTests extends AbstractHttpConfigTests { } createAppContext() FilterChainProxy fcp = appContext.getBean(BeanIds.FILTER_CHAIN_PROXY) - Map filterChains = fcp.getFilterChainMap(); + def filterChains = fcp.getFilterChains(); then: filterChains.size() == 2 - (filterChains.keySet() as List)[0].pattern == '/stateless/**' + filterChains[0].requestMatcher.pattern == '/stateless/**' } def duplicateHttpElementsAreRejected () { @@ -39,7 +41,8 @@ class MultiHttpBlockConfigTests extends AbstractHttpConfigTests { } createAppContext() then: - thrown(BeanDefinitionParsingException) + BeanCreationException e = thrown() + e.cause.cause instanceof IllegalArgumentException } def duplicatePatternsAreRejected () { @@ -52,7 +55,8 @@ class MultiHttpBlockConfigTests extends AbstractHttpConfigTests { } createAppContext() then: - thrown(BeanDefinitionParsingException) + BeanCreationException e = thrown() + e.cause instanceof IllegalArgumentException } @@ -64,9 +68,8 @@ class MultiHttpBlockConfigTests extends AbstractHttpConfigTests { 'form-login'() } createAppContext() - def fcp = appContext.getBean(BeanIds.FILTER_CHAIN_PROXY) - List filterChains = fcp.getFilterChainMap().values() as List; - List basicChain = filterChains[0]; + FilterChainProxy fcp = appContext.getBean(BeanIds.FILTER_CHAIN_PROXY) + SecurityFilterChain basicChain = fcp.filterChains[0]; expect: Assert.assertSame (basicChain, appContext.getBean('basic')) diff --git a/config/src/test/java/org/springframework/security/config/FilterChainProxyConfigTests.java b/config/src/test/java/org/springframework/security/config/FilterChainProxyConfigTests.java index 33d83e460d..43f77af321 100644 --- a/config/src/test/java/org/springframework/security/config/FilterChainProxyConfigTests.java +++ b/config/src/test/java/org/springframework/security/config/FilterChainProxyConfigTests.java @@ -34,6 +34,7 @@ import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.web.FilterChainProxy; +import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.context.SecurityContextPersistenceFilter; import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter; @@ -109,10 +110,10 @@ public class FilterChainProxyConfigTests { public void mixingPatternsAndPlaceholdersDoesntCauseOrderingIssues() throws Exception { FilterChainProxy fcp = appCtx.getBean("sec1235FilterChainProxy", FilterChainProxy.class); - RequestMatcher[] matchers = fcp.getFilterChainMap().keySet().toArray(new RequestMatcher[fcp.getFilterChainMap().keySet().size()]); - assertEquals("/login*", ((AntPathRequestMatcher)matchers[0]).getPattern()); - assertEquals("/logout", ((AntPathRequestMatcher)matchers[1]).getPattern()); - assertTrue(matchers[2] instanceof AnyRequestMatcher); + List 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); } private void checkPathAndFilterOrder(FilterChainProxy filterChainProxy) throws Exception { diff --git a/config/src/test/java/org/springframework/security/config/InvalidConfigurationTests.java b/config/src/test/java/org/springframework/security/config/InvalidConfigurationTests.java index 23218dde66..4061080d6e 100644 --- a/config/src/test/java/org/springframework/security/config/InvalidConfigurationTests.java +++ b/config/src/test/java/org/springframework/security/config/InvalidConfigurationTests.java @@ -43,13 +43,21 @@ public class InvalidConfigurationTests { try { setContext(""); } catch (BeanCreationException e) { - assertTrue(e.getCause().getCause() instanceof NoSuchBeanDefinitionException); - NoSuchBeanDefinitionException nsbe = (NoSuchBeanDefinitionException) e.getCause().getCause(); + Throwable cause = ultimateCause(e); + assertTrue(cause instanceof NoSuchBeanDefinitionException); + NoSuchBeanDefinitionException nsbe = (NoSuchBeanDefinitionException) cause; assertEquals(BeanIds.AUTHENTICATION_MANAGER, nsbe.getBeanName()); assertTrue(nsbe.getMessage().endsWith(AuthenticationManagerFactoryBean.MISSING_BEAN_ERROR_MESSAGE)); } } + private Throwable ultimateCause(Throwable e) { + if (e.getCause() == null) { + return e; + } + return ultimateCause(e.getCause()); + } + private void setContext(String context) { appContext = new InMemoryXmlApplicationContext(context); } diff --git a/config/src/test/resources/org/springframework/security/util/filtertest-valid.xml b/config/src/test/resources/org/springframework/security/util/filtertest-valid.xml index 12808f8b0a..1349ac5a53 100644 --- a/config/src/test/resources/org/springframework/security/util/filtertest-valid.xml +++ b/config/src/test/resources/org/springframework/security/util/filtertest-valid.xml @@ -19,9 +19,12 @@ --> + xsi:schemaLocation=" + http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd + http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd + http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd"> @@ -45,44 +48,52 @@ http://www.springframework.org/schema/security http://www.springframework.org/sc - - - - - + + + + + + + - - - - - - + + + + + + + + - - - - + + + + + + - + + - + + @@ -97,60 +108,70 @@ http://www.springframework.org/schema/security http://www.springframework.org/sc - - - - - + + + + + + + - - - - + + + + - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + + + - - - - - + + + + + + + - - - - - - - - - + + + + + + + + + + + diff --git a/web/src/main/java/org/springframework/security/web/SecurityFilterChain.java b/web/src/main/java/org/springframework/security/web/SecurityFilterChain.java index 86e8605954..fced2e0814 100644 --- a/web/src/main/java/org/springframework/security/web/SecurityFilterChain.java +++ b/web/src/main/java/org/springframework/security/web/SecurityFilterChain.java @@ -32,7 +32,7 @@ public final class SecurityFilterChain { public SecurityFilterChain(RequestMatcher requestMatcher, List filters) { this.requestMatcher = requestMatcher; - this.filters = filters; + this.filters = new ArrayList(filters); } public RequestMatcher getRequestMatcher() { diff --git a/web/src/main/java/org/springframework/security/web/util/AnyRequestMatcher.java b/web/src/main/java/org/springframework/security/web/util/AnyRequestMatcher.java index 34cd6c22b3..59432a8fb1 100644 --- a/web/src/main/java/org/springframework/security/web/util/AnyRequestMatcher.java +++ b/web/src/main/java/org/springframework/security/web/util/AnyRequestMatcher.java @@ -14,4 +14,13 @@ public final class AnyRequestMatcher implements RequestMatcher { return true; } + @Override + public boolean equals(Object obj) { + return obj instanceof AnyRequestMatcher; + } + + @Override + public int hashCode() { + return 1; + } }