2
0
Эх сурвалжийг харах

SEC-1657: Corresponding namespace updates to use SecurityFilterChain list in place of filterChainMap.

Luke Taylor 14 жил өмнө
parent
commit
04dc65c8fe
17 өөрчлөгдсөн 258 нэмэгдсэн , 174 устгасан
  1. 1 0
      config/src/main/java/org/springframework/security/config/BeanIds.java
  2. 5 4
      config/src/main/java/org/springframework/security/config/SecurityNamespaceHandler.java
  3. 5 10
      config/src/main/java/org/springframework/security/config/debug/DebugFilter.java
  4. 19 5
      config/src/main/java/org/springframework/security/config/http/DefaultFilterChainValidator.java
  5. 45 0
      config/src/main/java/org/springframework/security/config/http/FilterChainBeanDefinitionParser.java
  6. 5 5
      config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java
  7. 1 8
      config/src/main/java/org/springframework/security/config/http/HttpFirewallBeanDefinitionParser.java
  8. 50 57
      config/src/main/java/org/springframework/security/config/http/HttpSecurityBeanDefinitionParser.java
  9. 1 1
      config/src/main/resources/org/springframework/security/config/spring-security-3.1.rnc
  10. 6 6
      config/src/main/resources/org/springframework/security/config/spring-security-3.1.xsd
  11. 1 1
      config/src/main/resources/org/springframework/security/config/spring-security.xsl
  12. 10 7
      config/src/test/groovy/org/springframework/security/config/http/MultiHttpBlockConfigTests.groovy
  13. 5 4
      config/src/test/java/org/springframework/security/config/FilterChainProxyConfigTests.java
  14. 10 2
      config/src/test/java/org/springframework/security/config/InvalidConfigurationTests.java
  15. 84 63
      config/src/test/resources/org/springframework/security/util/filtertest-valid.xml
  16. 1 1
      web/src/main/java/org/springframework/security/web/SecurityFilterChain.java
  17. 9 0
      web/src/main/java/org/springframework/security/web/util/AnyRequestMatcher.java

+ 1 - 0
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";

+ 5 - 4
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();
         }
     }

+ 5 - 10
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<RequestMatcher, List<Filter>> 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<Filter> getFilters(HttpServletRequest request)  {
-        for (Map.Entry<RequestMatcher, List<Filter>> 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();
             }
         }
 

+ 19 - 5
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<Filter> filters : fcp.getFilterChainMap().values()) {
-            checkLoginPageIsntProtected(fcp, filters);
-            checkFilterStack(filters);
+        for(SecurityFilterChain filterChain : fcp.getFilterChains()) {
+            checkLoginPageIsntProtected(fcp, filterChain.getFilters());
+            checkFilterStack(filterChain.getFilters());
+        }
+
+        checkForDuplicateMatchers(new ArrayList<SecurityFilterChain>(fcp.getFilterChains()));
+    }
+
+    private void checkForDuplicateMatchers(List<SecurityFilterChain> 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 <http> namespace " +
+                        "elements, you must use a 'pattern' attribute to define the request patterns to which they apply.");
+            }
         }
     }
 

+ 45 - 0
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<RuntimeBeanReference> filterChain = new ManagedList<RuntimeBeanReference>(filterBeanNames.length);
+
+            for (String name : filterBeanNames) {
+                filterChain.add(new RuntimeBeanReference(name));
+            }
+
+            builder.addConstructorArgValue(filterChain);
+        }
+    }
+}

+ 5 - 5
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<BeanDefinition,BeanDefinition> 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));
         }

+ 1 - 8
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<BeanDefinition, BeanReference>(),
-                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));
 

+ 50 - 57
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 <tt>FilterChainProxy</tt> 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<BeanDefinition, BeanReference> filterChainMap = new ManagedMap<BeanDefinition, BeanReference>();
-
-        String filterChainPattern = element.getAttribute(ATT_PATH_PATTERN);
-
-        BeanDefinition filterChainMatcher;
-
-        if (StringUtils.hasText(filterChainPattern)) {
-            filterChainMatcher = matcherType.createMatcher(filterChainPattern, null);
-        } else {
-            filterChainMatcher = new RootBeanDefinition(AnyRequestMatcher.class);
-        }
+        registerFilterChainProxyIfNecessary(pc, pc.extractSource(element));
 
-        filterChainMap.put(filterChainMatcher, createFilterChain(element, pc, matcherType));
+        // Obtain the filter chains and add the new chain to it
+        BeanDefinition listFactoryBean = pc.getRegistry().getBeanDefinition(BeanIds.FILTER_CHAINS);
+        List<BeanReference> filterChains = (List<BeanReference>)
+                listFactoryBean.getPropertyValues().getPropertyValue("sourceList").getValue();
 
-        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 &lt;http&gt; 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<BeanReference> authenticationProviders = new ManagedList<BeanReference>();
         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<BeanMetadataElement> filterChain = new ManagedList<BeanMetadataElement>();
 
         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<BeanDefinition, BeanReference> 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<Integer,ValueHolder> 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 <http> 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);
     }
 
 }

+ 1 - 1
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}

+ 6 - 6
config/src/main/resources/org/springframework/security/config/spring-security-3.1.xsd

@@ -974,11 +974,7 @@
       <xs:documentation>Used to explicitly configure a FilterChainProxy instance with a FilterChainMap</xs:documentation>
     </xs:annotation><xs:complexType>
       <xs:sequence>
-        <xs:element maxOccurs="unbounded" name="filter-chain"><xs:annotation>
-      <xs:documentation>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.</xs:documentation>
-    </xs:annotation><xs:complexType>
-      <xs:attributeGroup ref="security:filter-chain.attlist"/>
-    </xs:complexType></xs:element>
+        <xs:element maxOccurs="unbounded" ref="security:filter-chain"/>
       </xs:sequence>
       <xs:attributeGroup ref="security:filter-chain-map.attlist"/>
     </xs:complexType></xs:element>
@@ -1004,7 +1000,11 @@
       </xs:simpleType>
     </xs:attribute>
   </xs:attributeGroup>
-  
+  <xs:element name="filter-chain"><xs:annotation>
+      <xs:documentation>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.</xs:documentation>
+    </xs:annotation><xs:complexType>
+      <xs:attributeGroup ref="security:filter-chain.attlist"/>
+    </xs:complexType></xs:element>
   <xs:attributeGroup name="filter-chain.attlist">
     <xs:attribute name="pattern" use="required" type="xs:token"/>
     <xs:attribute name="filters" use="required" type="xs:token"/>

+ 1 - 1
config/src/main/resources/org/springframework/security/config/spring-security.xsl

@@ -9,7 +9,7 @@
     <xsl:output method="xml" indent="yes"/>
 
     <xsl:variable name="elts-to-inline">
-        <xsl:text>,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,</xsl:text>
+        <xsl:text>,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,</xsl:text>
     </xsl:variable>
 
     <xsl:template match="xs:element">

+ 10 - 7
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 &lt;http&gt; 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'))

+ 5 - 4
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<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);
     }
 
     private void checkPathAndFilterOrder(FilterChainProxy filterChainProxy) throws Exception {

+ 10 - 2
config/src/test/java/org/springframework/security/config/InvalidConfigurationTests.java

@@ -43,13 +43,21 @@ public class InvalidConfigurationTests {
         try {
             setContext("<http auto-config='true' />");
         } 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);
     }

+ 84 - 63
config/src/test/resources/org/springframework/security/util/filtertest-valid.xml

@@ -19,9 +19,12 @@
 -->
 <beans default-lazy-init="true" xmlns="http://www.springframework.org/schema/beans"
     xmlns:sec="http://www.springframework.org/schema/security"
+    xmlns:util="http://www.springframework.org/schema/util"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
-http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd">
+    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">
 
     <bean id="mockFilter" class="org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter"/>
 
@@ -45,44 +48,52 @@ http://www.springframework.org/schema/security http://www.springframework.org/sc
     <bean id="mockNotAFilter" class="org.springframework.security.web.util.AnyRequestMatcher"/>
 
     <bean id="filterChain" class="org.springframework.security.web.FilterChainProxy">
-        <sec:filter-chain-map path-type="ant">
-            <sec:filter-chain pattern="/foo/**" filters="mockFilter"/>
-            <sec:filter-chain pattern="/some/other/path/**" filters="mockFilter"/>
-            <sec:filter-chain pattern="/do/not/filter" filters="none"/>
-        </sec:filter-chain-map>
+        <constructor-arg>
+           <util:list>
+               <sec:filter-chain pattern="/foo/**" filters="mockFilter"/>
+               <sec:filter-chain pattern="/some/other/path/**" filters="mockFilter"/>
+               <sec:filter-chain pattern="/do/not/filter" filters="none"/>
+           </util:list>
+        </constructor-arg>
     </bean>
 
 <!-- TODO: Refactor to replace the above (SEC-1034: 'new' is now the only valid syntax) -->
     <bean id="newFilterChainProxy" class="org.springframework.security.web.FilterChainProxy">
-        <sec:filter-chain-map path-type="ant">
-            <sec:filter-chain pattern="/foo/**" filters="mockFilter"/>
-            <sec:filter-chain pattern="/some/other/path/**"
-                filters="
-                sif,
-                mockFilter,
-                mockFilter2"
-                />
-            <sec:filter-chain pattern="/do/not/filter" filters="none"/>
-            <sec:filter-chain pattern="/**" filters="sif,apf,mockFilter"/>
-        </sec:filter-chain-map>
+        <constructor-arg>
+           <util:list>
+                <sec:filter-chain pattern="/foo/**" filters="mockFilter"/>
+                <sec:filter-chain pattern="/some/other/path/**"
+                    filters="
+                    sif,
+                    mockFilter,
+                    mockFilter2"
+                    />
+                <sec:filter-chain pattern="/do/not/filter" filters="none"/>
+                <sec:filter-chain pattern="/**" filters="sif,apf,mockFilter"/>
+           </util:list>
+        </constructor-arg>
     </bean>
 
     <bean id="newFilterChainProxyNoDefaultPath" class="org.springframework.security.web.FilterChainProxy">
-        <sec:filter-chain-map path-type="ant">
-            <sec:filter-chain pattern="/foo/**" filters="mockFilter"/>
-            <sec:filter-chain pattern="/*.bar" filters="mockFilter,mockFilter2"/>
-        </sec:filter-chain-map>
+        <constructor-arg>
+           <util:list>
+                <sec:filter-chain pattern="/foo/**" filters="mockFilter"/>
+                <sec:filter-chain pattern="/*.bar" filters="mockFilter,mockFilter2"/>
+           </util:list>
+        </constructor-arg>
     </bean>
 
     <bean id="newFilterChainProxyWrongPathOrder" class="org.springframework.security.web.FilterChainProxy">
-        <sec:filter-chain-map path-type="ant">
+        <constructor-arg>
+           <util:list>
             <sec:filter-chain pattern="/foo/**" filters="mockFilter"/>
             <sec:filter-chain pattern="/**" filters="
                                 sif,
                                 apf,
                                 mockFilter"/>
             <sec:filter-chain pattern="/some/other/path/**" filters="sif,mockFilter,mockFilter2"/>
-        </sec:filter-chain-map>
+           </util:list>
+        </constructor-arg>
     </bean>
 
     <bean id="newFilterChainProxyRegex" class="org.springframework.security.web.FilterChainProxy">
@@ -97,60 +108,70 @@ http://www.springframework.org/schema/security http://www.springframework.org/sc
     <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer" />
 
     <bean id="sec1235FilterChainProxy" class="org.springframework.security.web.FilterChainProxy">
-        <sec:filter-chain-map path-type="ant">
-        <sec:filter-chain pattern="${sec1235.pattern1}*" filters="sif,apf,mockFilter"/>
-        <sec:filter-chain pattern="${sec1235.pattern2}" filters="mockFilter2"/>
-        <sec:filter-chain pattern="/**" filters="sif"/>
-        </sec:filter-chain-map>
+        <constructor-arg>
+           <util:list>
+                <sec:filter-chain pattern="${sec1235.pattern1}*" filters="sif,apf,mockFilter"/>
+                <sec:filter-chain pattern="${sec1235.pattern2}" filters="mockFilter2"/>
+                <sec:filter-chain pattern="/**" filters="sif"/>
+           </util:list>
+        </constructor-arg>
     </bean>
 
     <bean id="newFilterChainProxyNonNamespace" class="org.springframework.security.web.FilterChainProxy">
-        <property name="filterChainMap">
-            <map>
-                <entry>
-                    <key>
+        <constructor-arg>
+            <list>
+                <bean class="org.springframework.security.web.SecurityFilterChain">
+                    <constructor-arg>
                         <bean class="org.springframework.security.web.util.AntPathRequestMatcher">
                             <constructor-arg value="/foo/**"/>
                         </bean>
-                    </key>
-                    <list>
-                      <ref local="mockFilter"/>
-                    </list>
-                </entry>
-                <entry>
-                    <key>
+                    </constructor-arg>
+                    <constructor-arg>
+                        <list>
+                          <ref local="mockFilter"/>
+                        </list>
+                    </constructor-arg>
+                </bean>
+                <bean class="org.springframework.security.web.SecurityFilterChain">
+                    <constructor-arg>
                         <bean class="org.springframework.security.web.util.AntPathRequestMatcher">
                             <constructor-arg value="/some/other/path/**"/>
                         </bean>
-                    </key>
-                    <list>
-                      <ref local="sif"/>
-                      <ref local="mockFilter"/>
-                      <ref local="mockFilter2"/>
-                    </list>
-                </entry>
-                <entry>
-                    <key>
+                    </constructor-arg>
+                    <constructor-arg>
+                        <list>
+                            <ref local="sif"/>
+                            <ref local="mockFilter"/>
+                            <ref local="mockFilter2"/>
+                        </list>
+                    </constructor-arg>
+                </bean>
+                <bean class="org.springframework.security.web.SecurityFilterChain">
+                    <constructor-arg>
                         <bean class="org.springframework.security.web.util.AntPathRequestMatcher">
                             <constructor-arg value="/do/not/filter*"/>
                         </bean>
-                    </key>
-                    <list/>
-                </entry>
-                <entry>
-                    <key>
+                    </constructor-arg>
+                    <constructor-arg>
+                        <list />
+                    </constructor-arg>
+                </bean>
+                <bean class="org.springframework.security.web.SecurityFilterChain">
+                    <constructor-arg>
                         <bean class="org.springframework.security.web.util.AntPathRequestMatcher">
                             <constructor-arg value="/**"/>
                         </bean>
-                    </key>
-                    <list>
-                      <ref local="sif"/>
-                      <ref local="apf"/>
-                      <ref local="mockFilter"/>
-                    </list>
-                </entry>
-            </map>
-        </property>
+                    </constructor-arg>
+                    <constructor-arg>
+                        <list>
+                          <ref local="sif"/>
+                          <ref local="apf"/>
+                          <ref local="mockFilter"/>
+                        </list>
+                    </constructor-arg>
+                </bean>
+            </list>
+        </constructor-arg>
     </bean>
 
 </beans>

+ 1 - 1
web/src/main/java/org/springframework/security/web/SecurityFilterChain.java

@@ -32,7 +32,7 @@ public final class SecurityFilterChain {
 
     public SecurityFilterChain(RequestMatcher requestMatcher, List<Filter> filters) {
         this.requestMatcher = requestMatcher;
-        this.filters = filters;
+        this.filters = new ArrayList<Filter>(filters);
     }
 
     public RequestMatcher getRequestMatcher() {

+ 9 - 0
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;
+    }
 }