Browse Source

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

Luke Taylor 14 năm trước cách đây
mục cha
commit
04dc65c8fe
17 tập tin đã thay đổi với 258 bổ sung174 xóa
  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;
+    }
 }