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

This commit is contained in:
Luke Taylor 2011-04-23 22:28:19 +01:00
parent 37d0454fd7
commit 04dc65c8fe
17 changed files with 258 additions and 174 deletions

View File

@ -25,6 +25,7 @@ public abstract class BeanIds {
public static final String METHOD_ACCESS_MANAGER = PREFIX + "defaultMethodAccessManager"; public static final String METHOD_ACCESS_MANAGER = PREFIX + "defaultMethodAccessManager";
public static final String FILTER_CHAIN_PROXY = PREFIX + "filterChainProxy"; 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 METHOD_SECURITY_METADATA_SOURCE_ADVISOR = PREFIX + "methodSecurityMetadataSourceAdvisor";
public static final String EMBEDDED_APACHE_DS = PREFIX + "apacheDirectoryServerContainer"; public static final String EMBEDDED_APACHE_DS = PREFIX + "apacheDirectoryServerContainer";

View File

@ -1,8 +1,5 @@
package org.springframework.security.config; package org.springframework.security.config;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.config.BeanDefinition; 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.AuthenticationProviderBeanDefinitionParser;
import org.springframework.security.config.authentication.JdbcUserServiceBeanDefinitionParser; import org.springframework.security.config.authentication.JdbcUserServiceBeanDefinitionParser;
import org.springframework.security.config.authentication.UserServiceBeanDefinitionParser; 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.FilterChainMapBeanDefinitionDecorator;
import org.springframework.security.config.http.FilterInvocationSecurityMetadataSourceParser; import org.springframework.security.config.http.FilterInvocationSecurityMetadataSourceParser;
import org.springframework.security.config.http.HttpFirewallBeanDefinitionParser; 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.Element;
import org.w3c.dom.Node; import org.w3c.dom.Node;
import java.util.*;
/** /**
* Parses elements from the "security" namespace (http://www.springframework.org/schema/security). * 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 (parser == null) {
if (Elements.HTTP.equals(name) || Elements.FILTER_SECURITY_METADATA_SOURCE.equals(name) || 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); reportMissingWebClasses(name, pc, element);
} else { } else {
reportUnsupportedNodeType(name, pc, element); reportUnsupportedNodeType(name, pc, element);
@ -147,6 +147,7 @@ public final class SecurityNamespaceHandler implements NamespaceHandler {
parsers.put(Elements.HTTP_FIREWALL, new HttpFirewallBeanDefinitionParser()); parsers.put(Elements.HTTP_FIREWALL, new HttpFirewallBeanDefinitionParser());
parsers.put(Elements.FILTER_INVOCATION_DEFINITION_SOURCE, new FilterInvocationSecurityMetadataSourceParser()); parsers.put(Elements.FILTER_INVOCATION_DEFINITION_SOURCE, new FilterInvocationSecurityMetadataSourceParser());
parsers.put(Elements.FILTER_SECURITY_METADATA_SOURCE, new FilterInvocationSecurityMetadataSourceParser()); parsers.put(Elements.FILTER_SECURITY_METADATA_SOURCE, new FilterInvocationSecurityMetadataSourceParser());
parsers.put(Elements.FILTER_CHAIN, new FilterChainBeanDefinitionParser());
filterChainMapBDD = new FilterChainMapBeanDefinitionDecorator(); filterChainMapBDD = new FilterChainMapBeanDefinitionDecorator();
} }
} }

View File

@ -1,7 +1,7 @@
package org.springframework.security.config.debug; package org.springframework.security.config.debug;
import org.springframework.security.web.FilterChainProxy; 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.security.web.util.UrlUtils;
import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.filter.OncePerRequestFilter;
@ -13,8 +13,7 @@ import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSession;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.*;
import java.util.Map;
/** /**
* Spring Security debugging filter. * Spring Security debugging filter.
@ -28,12 +27,10 @@ import java.util.Map;
*/ */
class DebugFilter extends OncePerRequestFilter { class DebugFilter extends OncePerRequestFilter {
private final FilterChainProxy fcp; private final FilterChainProxy fcp;
private final Map<RequestMatcher, List<Filter>> filterChainMap;
private final Logger logger = new Logger(); private final Logger logger = new Logger();
public DebugFilter(FilterChainProxy fcp) { public DebugFilter(FilterChainProxy fcp) {
this.fcp = fcp; this.fcp = fcp;
this.filterChainMap = fcp.getFilterChainMap();
} }
@Override @Override
@ -67,11 +64,9 @@ class DebugFilter extends OncePerRequestFilter {
} }
private List<Filter> getFilters(HttpServletRequest request) { private List<Filter> getFilters(HttpServletRequest request) {
for (Map.Entry<RequestMatcher, List<Filter>> entry : filterChainMap.entrySet()) { for (SecurityFilterChain chain : fcp.getFilterChains()) {
RequestMatcher matcher = entry.getKey(); if (chain.matches(request)) {
return chain.getFilters();
if (matcher.matches(request)) {
return entry.getValue();
} }
} }

View File

@ -1,7 +1,6 @@
package org.springframework.security.config.http; package org.springframework.security.config.http;
import java.util.Collection; import java.util.*;
import java.util.List;
import javax.servlet.Filter; import javax.servlet.Filter;
@ -12,6 +11,7 @@ import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.web.FilterChainProxy; import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.FilterInvocation; 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.ExceptionTranslationFilter;
import org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource; import org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor; 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()); private final Log logger = LogFactory.getLog(getClass());
public void validate(FilterChainProxy fcp) { public void validate(FilterChainProxy fcp) {
for(List<Filter> filters : fcp.getFilterChainMap().values()) { for(SecurityFilterChain filterChain : fcp.getFilterChains()) {
checkLoginPageIsntProtected(fcp, filters); checkLoginPageIsntProtected(fcp, filterChain.getFilters());
checkFilterStack(filters); 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.");
}
} }
} }

View File

@ -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);
}
}
}

View File

@ -95,13 +95,13 @@ class HttpConfigurationBuilder {
private BeanReference fsi; private BeanReference fsi;
private BeanReference requestCache; private BeanReference requestCache;
public HttpConfigurationBuilder(Element element, ParserContext pc, MatcherType matcherType, public HttpConfigurationBuilder(Element element, ParserContext pc,
String portMapperName, BeanReference authenticationManager) { String portMapperName, BeanReference authenticationManager) {
this.httpElt = element; this.httpElt = element;
this.pc = pc; this.pc = pc;
this.portMapperName = portMapperName; this.portMapperName = portMapperName;
this.matcherType = matcherType; this.matcherType = MatcherType.fromElement(element);
interceptUrls = DomUtils.getChildElementsByTagName(element, Elements.INTERCEPT_URL); interceptUrls = DomUtils.getChildElementsByTagName(element, Elements.INTERCEPT_URL);
for (Element urlElt : interceptUrls) { for (Element urlElt : interceptUrls) {
@ -339,7 +339,7 @@ class HttpConfigurationBuilder {
servApiFilter = new RootBeanDefinition(SecurityContextHolderAwareRequestFilter.class); servApiFilter = new RootBeanDefinition(SecurityContextHolderAwareRequestFilter.class);
} }
} }
// Adds the jaas-api integration filter if required // Adds the jaas-api integration filter if required
private void createJaasApiFilter() { private void createJaasApiFilter() {
final String ATT_JAAS_API_PROVISION = "jaas-api-provision"; final String ATT_JAAS_API_PROVISION = "jaas-api-provision";
@ -354,7 +354,7 @@ class HttpConfigurationBuilder {
jaasApiFilter = new RootBeanDefinition(JaasApiIntegrationFilter.class); jaasApiFilter = new RootBeanDefinition(JaasApiIntegrationFilter.class);
} }
} }
private void createChannelProcessingFilter() { private void createChannelProcessingFilter() {
ManagedMap<BeanDefinition,BeanDefinition> channelRequestMap = parseInterceptUrlsForChannelSecurity(); ManagedMap<BeanDefinition,BeanDefinition> channelRequestMap = parseInterceptUrlsForChannelSecurity();
@ -534,7 +534,7 @@ class HttpConfigurationBuilder {
if (jaasApiFilter != null) { if (jaasApiFilter != null) {
filters.add(new OrderDecorator(jaasApiFilter, JAAS_API_SUPPORT_FILTER)); filters.add(new OrderDecorator(jaasApiFilter, JAAS_API_SUPPORT_FILTER));
} }
if (sfpf != null) { if (sfpf != null) {
filters.add(new OrderDecorator(sfpf, SESSION_MANAGEMENT_FILTER)); filters.add(new OrderDecorator(sfpf, SESSION_MANAGEMENT_FILTER));
} }

View File

@ -1,18 +1,13 @@
package org.springframework.security.config.http; package org.springframework.security.config.http;
import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.factory.config.BeanDefinition; 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.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.ManagedMap;
import org.springframework.beans.factory.xml.BeanDefinitionParser; import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext; import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.security.config.BeanIds; import org.springframework.security.config.BeanIds;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.w3c.dom.Element; import org.w3c.dom.Element;
import java.util.*;
/** /**
* Injects the supplied {@code HttpFirewall} bean reference into the {@code FilterChainProxy}. * 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. // Ensure the FCP is registered.
HttpSecurityBeanDefinitionParser.registerFilterChainProxy(pc, HttpSecurityBeanDefinitionParser.registerFilterChainProxyIfNecessary(pc, pc.extractSource(element));
new ManagedMap<BeanDefinition, BeanReference>(),
pc.extractSource(element));
BeanDefinition filterChainProxy = pc.getRegistry().getBeanDefinition(BeanIds.FILTER_CHAIN_PROXY); BeanDefinition filterChainProxy = pc.getRegistry().getBeanDefinition(BeanIds.FILTER_CHAIN_PROXY);
filterChainProxy.getPropertyValues().addPropertyValue("firewall", new RuntimeBeanReference(ref)); filterChainProxy.getPropertyValues().addPropertyValue("firewall", new RuntimeBeanReference(ref));

View File

@ -1,10 +1,5 @@
package org.springframework.security.config.http; 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.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanMetadataElement; 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.ListFactoryBean;
import org.springframework.beans.factory.config.MethodInvokingFactoryBean; import org.springframework.beans.factory.config.MethodInvokingFactoryBean;
import org.springframework.beans.factory.config.RuntimeBeanReference; 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.BeanComponentDefinition;
import org.springframework.beans.factory.parsing.CompositeComponentDefinition; import org.springframework.beans.factory.parsing.CompositeComponentDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.ManagedList; 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.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser; import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext; 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.Elements;
import org.springframework.security.config.authentication.AuthenticationManagerFactoryBean; import org.springframework.security.config.authentication.AuthenticationManagerFactoryBean;
import org.springframework.security.web.FilterChainProxy; import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.util.AnyRequestMatcher; import org.springframework.security.web.util.AnyRequestMatcher;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils; import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element; import org.w3c.dom.Element;
import java.util.*;
/** /**
* Sets up HTTP security: filter stack and protected URLs. * 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 * 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. * 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) { public BeanDefinition parse(Element element, ParserContext pc) {
CompositeComponentDefinition compositeDef = CompositeComponentDefinition compositeDef =
new CompositeComponentDefinition(element.getTagName(), pc.extractSource(element)); new CompositeComponentDefinition(element.getTagName(), pc.extractSource(element));
pc.pushContainingComponent(compositeDef); pc.pushContainingComponent(compositeDef);
MatcherType matcherType = MatcherType.fromElement(element); registerFilterChainProxyIfNecessary(pc, pc.extractSource(element));
ManagedMap<BeanDefinition, BeanReference> filterChainMap = new ManagedMap<BeanDefinition, BeanReference>();
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<BeanReference> filterChains = (List<BeanReference>)
listFactoryBean.getPropertyValues().getPropertyValue("sourceList").getValue();
BeanDefinition filterChainMatcher; filterChains.add(createFilterChain(element, pc));
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));
pc.popAndRegisterContainingComponent(); pc.popAndRegisterContainingComponent();
return null; 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)); boolean secured = !OPT_SECURITY_NONE.equals(element.getAttribute(ATT_SECURED));
if (!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); final String portMapperName = createPortMapper(element, pc);
@ -117,7 +109,7 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
ManagedList<BeanReference> authenticationProviders = new ManagedList<BeanReference>(); ManagedList<BeanReference> authenticationProviders = new ManagedList<BeanReference>();
BeanReference authenticationManager = createAuthenticationManager(element, pc, authenticationProviders); BeanReference authenticationManager = createAuthenticationManager(element, pc, authenticationProviders);
HttpConfigurationBuilder httpBldr = new HttpConfigurationBuilder(element, pc, matcherType, HttpConfigurationBuilder httpBldr = new HttpConfigurationBuilder(element, pc,
portMapperName, authenticationManager); portMapperName, authenticationManager);
AuthenticationConfigBuilder authBldr = new AuthenticationConfigBuilder(element, pc, AuthenticationConfigBuilder authBldr = new AuthenticationConfigBuilder(element, pc,
@ -135,27 +127,41 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
Collections.sort(unorderedFilterChain, new OrderComparator()); Collections.sort(unorderedFilterChain, new OrderComparator());
checkFilterChainOrder(unorderedFilterChain, pc, pc.extractSource(element)); checkFilterChainOrder(unorderedFilterChain, pc, pc.extractSource(element));
// The list of filter beans
List<BeanMetadataElement> filterChain = new ManagedList<BeanMetadataElement>(); List<BeanMetadataElement> filterChain = new ManagedList<BeanMetadataElement>();
for (OrderDecorator od : unorderedFilterChain) { for (OrderDecorator od : unorderedFilterChain) {
filterChain.add(od.bean); filterChain.add(od.bean);
} }
return createFilterListBean(element, pc, filterChain); return createSecurityFilterChainBean(element, pc, filterChain);
} }
private BeanReference createFilterListBean(Element element, ParserContext pc, List<?> filterChain) { private BeanReference createSecurityFilterChainBean(Element element, ParserContext pc, List<?> filterChain) {
BeanDefinition listFactoryBean = new RootBeanDefinition(ListFactoryBean.class); 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"); String id = element.getAttribute("name");
if (!StringUtils.hasText(id)) { if (!StringUtils.hasText(id)) {
id = element.getAttribute("id"); id = element.getAttribute("id");
if (!StringUtils.hasText(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); return new RuntimeBeanReference(id);
} }
@ -262,35 +268,22 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
return customFilters; return customFilters;
} }
@SuppressWarnings("unchecked") static void registerFilterChainProxyIfNecessary(ParserContext pc, Object source) {
static void registerFilterChainProxy(ParserContext pc, Map<BeanDefinition, BeanReference> filterChainMap, Object source) {
if (pc.getRegistry().containsBeanDefinition(BeanIds.FILTER_CHAIN_PROXY)) { if (pc.getRegistry().containsBeanDefinition(BeanIds.FILTER_CHAIN_PROXY)) {
// Already registered. Obtain the filter chain map and add the new entries to it return;
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);
} }
// 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);
} }
} }

View File

@ -443,7 +443,7 @@ filter-chain-map.attlist &=
request-matcher? request-matcher?
filter-chain = 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} element filter-chain {filter-chain.attlist, empty}
filter-chain.attlist &= filter-chain.attlist &=
attribute pattern {xsd:token} attribute pattern {xsd:token}

View File

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

View File

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

View File

@ -4,6 +4,8 @@ import org.springframework.beans.factory.parsing.BeanDefinitionParsingException
import org.springframework.security.config.BeanIds import org.springframework.security.config.BeanIds
import org.springframework.security.web.FilterChainProxy import org.springframework.security.web.FilterChainProxy
import org.junit.Assert import org.junit.Assert
import org.springframework.beans.factory.BeanCreationException
import org.springframework.security.web.SecurityFilterChain
/** /**
* Tests scenarios with multiple &lt;http&gt; elements. * Tests scenarios with multiple &lt;http&gt; elements.
@ -22,11 +24,11 @@ class MultiHttpBlockConfigTests extends AbstractHttpConfigTests {
} }
createAppContext() createAppContext()
FilterChainProxy fcp = appContext.getBean(BeanIds.FILTER_CHAIN_PROXY) FilterChainProxy fcp = appContext.getBean(BeanIds.FILTER_CHAIN_PROXY)
Map filterChains = fcp.getFilterChainMap(); def filterChains = fcp.getFilterChains();
then: then:
filterChains.size() == 2 filterChains.size() == 2
(filterChains.keySet() as List)[0].pattern == '/stateless/**' filterChains[0].requestMatcher.pattern == '/stateless/**'
} }
def duplicateHttpElementsAreRejected () { def duplicateHttpElementsAreRejected () {
@ -39,7 +41,8 @@ class MultiHttpBlockConfigTests extends AbstractHttpConfigTests {
} }
createAppContext() createAppContext()
then: then:
thrown(BeanDefinitionParsingException) BeanCreationException e = thrown()
e.cause.cause instanceof IllegalArgumentException
} }
def duplicatePatternsAreRejected () { def duplicatePatternsAreRejected () {
@ -52,7 +55,8 @@ class MultiHttpBlockConfigTests extends AbstractHttpConfigTests {
} }
createAppContext() createAppContext()
then: then:
thrown(BeanDefinitionParsingException) BeanCreationException e = thrown()
e.cause instanceof IllegalArgumentException
} }
@ -64,9 +68,8 @@ class MultiHttpBlockConfigTests extends AbstractHttpConfigTests {
'form-login'() 'form-login'()
} }
createAppContext() createAppContext()
def fcp = appContext.getBean(BeanIds.FILTER_CHAIN_PROXY) FilterChainProxy fcp = appContext.getBean(BeanIds.FILTER_CHAIN_PROXY)
List filterChains = fcp.getFilterChainMap().values() as List; SecurityFilterChain basicChain = fcp.filterChains[0];
List basicChain = filterChains[0];
expect: expect:
Assert.assertSame (basicChain, appContext.getBean('basic')) Assert.assertSame (basicChain, appContext.getBean('basic'))

View File

@ -34,6 +34,7 @@ import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.web.FilterChainProxy; import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.context.SecurityContextPersistenceFilter; import org.springframework.security.web.context.SecurityContextPersistenceFilter;
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter; import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter;
@ -109,10 +110,10 @@ public class FilterChainProxyConfigTests {
public void mixingPatternsAndPlaceholdersDoesntCauseOrderingIssues() throws Exception { public void mixingPatternsAndPlaceholdersDoesntCauseOrderingIssues() throws Exception {
FilterChainProxy fcp = appCtx.getBean("sec1235FilterChainProxy", FilterChainProxy.class); FilterChainProxy fcp = appCtx.getBean("sec1235FilterChainProxy", FilterChainProxy.class);
RequestMatcher[] matchers = fcp.getFilterChainMap().keySet().toArray(new RequestMatcher[fcp.getFilterChainMap().keySet().size()]); List<SecurityFilterChain> chains = fcp.getFilterChains();
assertEquals("/login*", ((AntPathRequestMatcher)matchers[0]).getPattern()); assertEquals("/login*", ((AntPathRequestMatcher)chains.get(0).getRequestMatcher()).getPattern());
assertEquals("/logout", ((AntPathRequestMatcher)matchers[1]).getPattern()); assertEquals("/logout", ((AntPathRequestMatcher)chains.get(1).getRequestMatcher()).getPattern());
assertTrue(matchers[2] instanceof AnyRequestMatcher); assertTrue(chains.get(2).getRequestMatcher() instanceof AnyRequestMatcher);
} }
private void checkPathAndFilterOrder(FilterChainProxy filterChainProxy) throws Exception { private void checkPathAndFilterOrder(FilterChainProxy filterChainProxy) throws Exception {

View File

@ -43,13 +43,21 @@ public class InvalidConfigurationTests {
try { try {
setContext("<http auto-config='true' />"); setContext("<http auto-config='true' />");
} catch (BeanCreationException e) { } catch (BeanCreationException e) {
assertTrue(e.getCause().getCause() instanceof NoSuchBeanDefinitionException); Throwable cause = ultimateCause(e);
NoSuchBeanDefinitionException nsbe = (NoSuchBeanDefinitionException) e.getCause().getCause(); assertTrue(cause instanceof NoSuchBeanDefinitionException);
NoSuchBeanDefinitionException nsbe = (NoSuchBeanDefinitionException) cause;
assertEquals(BeanIds.AUTHENTICATION_MANAGER, nsbe.getBeanName()); assertEquals(BeanIds.AUTHENTICATION_MANAGER, nsbe.getBeanName());
assertTrue(nsbe.getMessage().endsWith(AuthenticationManagerFactoryBean.MISSING_BEAN_ERROR_MESSAGE)); 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) { private void setContext(String context) {
appContext = new InMemoryXmlApplicationContext(context); appContext = new InMemoryXmlApplicationContext(context);
} }

View File

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

View File

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

View File

@ -14,4 +14,13 @@ public final class AnyRequestMatcher implements RequestMatcher {
return true; return true;
} }
@Override
public boolean equals(Object obj) {
return obj instanceof AnyRequestMatcher;
}
@Override
public int hashCode() {
return 1;
}
} }