SEC-578: Refactored to remove FilterChainMap and use a LinkedHashMap instead to maintain the path ordering. Also made use of Springs ManagedList and ManagedMap to preform resolution of bean names to Filter objects at runtime, replacing the unnecessary bean which was performing this task for the filter lists.

This commit is contained in:
Luke Taylor 2007-10-22 23:52:29 +00:00
parent c2db942852
commit acf3966651
9 changed files with 312 additions and 164 deletions

View File

@ -1,8 +1,11 @@
package org.springframework.security.config;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.support.ManagedMap;
import org.springframework.beans.factory.xml.BeanDefinitionDecorator;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.context.ApplicationContext;
@ -15,10 +18,7 @@ import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import javax.servlet.Filter;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.*;
/**
* Sets the FilterChainMap for a FilterChainProxy bean declaration.
@ -29,82 +29,45 @@ import java.util.List;
public class FilterChainMapBeanDefinitionDecorator implements BeanDefinitionDecorator {
public static final String FILTER_CHAIN_ELT_NAME = "filter-chain";
public BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder definition, ParserContext parserContext) {
FilterChainMap filterChainMap = new FilterChainMap();
public BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder holder, ParserContext parserContext) {
BeanDefinition filterChainProxy = holder.getBeanDefinition();
Map filterChainMap = new LinkedHashMap();
Element elt = (Element)node;
String pathType = elt.getAttribute(HttpSecurityBeanDefinitionParser.PATTERN_TYPE_ATTRIBUTE);
if (HttpSecurityBeanDefinitionParser.PATTERN_TYPE_REGEX.equals(pathType)) {
filterChainMap.setUrlPathMatcher(new RegexUrlPathMatcher());
filterChainProxy.getPropertyValues().addPropertyValue("matcher", new RegexUrlPathMatcher());
}
List paths = new ArrayList();
List filterChains = new ArrayList();
Iterator filterChainElts = DomUtils.getChildElementsByTagName(elt, FILTER_CHAIN_ELT_NAME).iterator();
while (filterChainElts.hasNext()) {
Element chain = (Element) filterChainElts.next();
String path = chain.getAttribute(HttpSecurityBeanDefinitionParser.PATH_PATTERN_ATTRIBUTE);
Assert.hasText(path, "The attribute '" + HttpSecurityBeanDefinitionParser.PATH_PATTERN_ATTRIBUTE + "' must not be empty");
Assert.hasText(path, "The attribute '" + HttpSecurityBeanDefinitionParser.PATH_PATTERN_ATTRIBUTE +
"' must not be empty");
String filters = chain.getAttribute(HttpSecurityBeanDefinitionParser.FILTERS_ATTRIBUTE);
Assert.hasText(filters, "The attribute '" + HttpSecurityBeanDefinitionParser.FILTERS_ATTRIBUTE +
"'must not be empty");
paths.add(path);
filterChains.add(filters);
}
// Set the FilterChainMap on the FilterChainProxy bean.
definition.getBeanDefinition().getPropertyValues().addPropertyValue("filterChainMap", filterChainMap);
if (filters.equals(HttpSecurityBeanDefinitionParser.NO_FILTERS_VALUE)) {
filterChainMap.put(path, Collections.EMPTY_LIST);
} else {
String[] filterBeanNames = StringUtils.tokenizeToStringArray(filters, ",");
ManagedList filterChain = new ManagedList(filterBeanNames.length);
// Register the ApplicationContextAware bean which will add the filter chains to the FilterChainMap
RootBeanDefinition chainResolver = new RootBeanDefinition(FilterChainResolver.class);
chainResolver.getConstructorArgumentValues().addIndexedArgumentValue(0, filterChainMap);
chainResolver.getConstructorArgumentValues().addIndexedArgumentValue(1, paths);
chainResolver.getConstructorArgumentValues().addIndexedArgumentValue(2, filterChains);
parserContext.getRegistry().registerBeanDefinition(definition.getBeanName() + ".filterChainMapChainResolver",
chainResolver);
return definition;
}
/**
* Bean which stores the filter chains as lists of bean names (e.g.
* "filter1, filter2, filter3") until the application context is available, then resolves them
* to actual Filter instances when the <tt>setApplicationContext</tt> method is called.
* It then uses them to build the secure URL configuration for the supplied FilterChainMap.
*/
static class FilterChainResolver implements ApplicationContextAware {
private List paths;
private List filterChains;
FilterChainMap filterChainMap;
FilterChainResolver(FilterChainMap filterChainMap, List paths, List filterChains) {
this.paths = paths;
this.filterChains = filterChains;
this.filterChainMap = filterChainMap;
}
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
for (int i=0; i < paths.size(); i++) {
String path = (String)paths.get(i);
String filterList = (String) filterChains.get(i);
if (filterList.equals(HttpSecurityBeanDefinitionParser.NO_FILTERS_VALUE)) {
filterChainMap.addSecureUrl(path, HttpSecurityBeanDefinitionParser.EMPTY_FILTER_CHAIN);
} else {
String[] filterNames = StringUtils.tokenizeToStringArray(filterList, ",");
Filter[] filters = new Filter[filterNames.length];
for (int j=0; j < filterNames.length; j++) {
filters[j] = (Filter) applicationContext.getBean(filterNames[j], Filter.class);
}
filterChainMap.addSecureUrl(path, filters);
for (int i=0; i < filterBeanNames.length; i++) {
filterChain.add(new RuntimeBeanReference(filterBeanNames[i]));
}
filterChainMap.put(path, filterChain);
}
}
filterChainProxy.getPropertyValues().addPropertyValue("filterChainMap", new ManagedMap(filterChainMap));
return holder;
}
}

View File

@ -9,7 +9,10 @@ import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.security.ConfigAttributeDefinition;
import org.springframework.security.ConfigAttributeEditor;
import org.springframework.security.context.HttpSessionContextIntegrationFilter;
import org.springframework.security.intercept.web.*;
import org.springframework.security.intercept.web.FilterInvocationDefinitionMap;
import org.springframework.security.intercept.web.FilterSecurityInterceptor;
import org.springframework.security.intercept.web.PathBasedFilterInvocationDefinitionMap;
import org.springframework.security.intercept.web.RegExpBasedFilterInvocationDefinitionMap;
import org.springframework.security.ui.ExceptionTranslationFilter;
import org.springframework.security.util.FilterChainProxy;
import org.springframework.security.util.RegexUrlPathMatcher;
@ -19,8 +22,7 @@ import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;
import javax.servlet.Filter;
import java.util.Iterator;
import java.util.List;
import java.util.*;
/**
* Sets up HTTP security: filter stack and protected URLs.
@ -48,7 +50,6 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
static final String FILTERS_ATTRIBUTE = "filters";
static final String NO_FILTERS_VALUE = "none";
static final Filter[] EMPTY_FILTER_CHAIN = new Filter[0];
private static final String ACCESS_CONFIG_ATTRIBUTE = "access";
@ -71,14 +72,14 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
// TODO: Get path type attribute and determine FilDefInvS class
FilterChainMap filterChainMap = new FilterChainMap();
Map filterChainMap = new LinkedHashMap();
String patternType = element.getAttribute(PATTERN_TYPE_ATTRIBUTE);
FilterInvocationDefinitionMap interceptorFilterInvDefSource = new PathBasedFilterInvocationDefinitionMap();
if (patternType.equals(PATTERN_TYPE_REGEX)) {
filterChainMap.setUrlPathMatcher(new RegexUrlPathMatcher());
filterChainProxy.getPropertyValues().addPropertyValue("matcher", new RegexUrlPathMatcher());
interceptorFilterInvDefSource = new RegExpBasedFilterInvocationDefinitionMap();
}
@ -131,10 +132,10 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
}
/**
* Parses the intercept-url elements and populates the FilterChainProxy's FilterChainMap and the
* Parses the intercept-url elements and populates the FilterChainProxy's filter chain Map and the
* FilterInvocationDefinitionSource used in FilterSecurityInterceptor.
*/
private void parseInterceptUrls(List urlElts, FilterChainMap filterChainMap,
private void parseInterceptUrls(List urlElts, Map filterChainMap,
FilterInvocationDefinitionMap interceptorFilterInvDefSource) {
Iterator urlEltsIterator = urlElts.iterator();
@ -166,7 +167,7 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
throw new IllegalStateException("Currently only 'none' is supported as the custom filters attribute");
}
filterChainMap.addSecureUrl(path, EMPTY_FILTER_CHAIN);
filterChainMap.put(path, Collections.EMPTY_LIST);
}
}
}

View File

@ -79,11 +79,14 @@ public class HttpSecurityConfigPostProcessor implements BeanFactoryPostProcessor
// Set the default match
List defaultFilterChain = orderFilters(beanFactory);
FilterChainMap filterMap = filterChainProxy.getFilterChainMap();
// Note that this returns a copy
Map filterMap = filterChainProxy.getFilterChainMap();
String allUrlsMatch = filterMap.getMatcher().getUniversalMatchPattern();
String allUrlsMatch = filterChainProxy.getMatcher().getUniversalMatchPattern();
filterMap.addSecureUrl(allUrlsMatch, (Filter[]) defaultFilterChain.toArray(new Filter[0]));
filterMap.put(allUrlsMatch, defaultFilterChain);
filterChainProxy.setFilterChainMap(filterMap);
}
private List orderFilters(ConfigurableListableBeanFactory beanFactory) {

View File

@ -1,6 +1,14 @@
package org.springframework.security.config;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.security.util.FilterChainProxy;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;
/**
@ -10,6 +18,8 @@ import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
* @version $Id$
*/
public class SecurityNamespaceHandler extends NamespaceHandlerSupport {
public static final String DEFAULT_FILTER_CHAIN_PROXY_ID = "_filterChainProxy";
public void init() {
registerBeanDefinitionParser("ldap", new LdapBeanDefinitionParser());
registerBeanDefinitionParser("http", new HttpSecurityBeanDefinitionParser());
@ -18,4 +28,19 @@ public class SecurityNamespaceHandler extends NamespaceHandlerSupport {
registerBeanDefinitionDecorator("intercept-methods", new InterceptMethodsBeanDefinitionDecorator());
registerBeanDefinitionDecorator("filter-chain-map", new FilterChainMapBeanDefinitionDecorator());
}
// private class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
//
// public BeanDefinition parse(Element element, ParserContext parserContext) {
// RootBeanDefinition filterChainProxy = new RootBeanDefinition(FilterChainProxy.class);
//
// Element formLoginElt = DomUtils.getChildElementByTagName(element, "form-login");
//
// if (formLoginElt != null) {
// parse(formLoginElt, parserContext);
// }
//
// return filterChainProxy;
// }
// }
}

View File

@ -4,26 +4,24 @@ import org.springframework.context.ApplicationContext;
import org.springframework.security.ConfigAttribute;
import org.springframework.security.ConfigAttributeDefinition;
import org.springframework.security.util.FilterChainProxy;
import org.springframework.security.util.RegexUrlPathMatcher;
import javax.servlet.Filter;
import java.util.List;
import java.util.Iterator;
import java.util.ArrayList;
import java.util.*;
/**
* Used internally to provide backward compatibility for configuration of FilterChainProxy using a
* FilterInvocationDefinitionSource. This is deprecated in favour of namespace-based configuration.
*
* This class will convert a FilterInvocationDefinitionSource into a FilterChainMap, provided it is one of the
* recognised implementations (ant path or regular expression).
* This class will convert a FilterInvocationDefinitionSource into a suitable Map, provided it is one of the
* recognised implementations (ant path or regular expression). The order of the mappings will be
* preserved in the Map.
*
* @author Luke Taylor
* @version $Id$
*/
public class FIDSToFilterChainMapConverter {
private FilterChainMap filterChainMap = new FilterChainMap();
private LinkedHashMap filterChainMap = new LinkedHashMap();
public FIDSToFilterChainMapConverter(FilterInvocationDefinitionSource fids, ApplicationContext appContext) {
@ -39,7 +37,6 @@ public class FIDSToFilterChainMapConverter {
requestMap = ((PathBasedFilterInvocationDefinitionMap)fids).getRequestMap();
} else if (fids instanceof RegExpBasedFilterInvocationDefinitionMap) {
requestMap = ((RegExpBasedFilterInvocationDefinitionMap)fids).getRequestMap();
filterChainMap.setUrlPathMatcher(new RegexUrlPathMatcher());
} else {
throw new IllegalArgumentException("Can't handle FilterInvocationDefinitionSource type " + fids.getClass());
}
@ -77,11 +74,11 @@ public class FIDSToFilterChainMapConverter {
}
}
filterChainMap.addSecureUrl(path, (Filter[]) filters.toArray(new Filter[filters.size()]));
filterChainMap.put(path, filters);
}
}
public FilterChainMap getFilterChainMap() {
public Map getFilterChainMap() {
return filterChainMap;
}
}

View File

@ -15,30 +15,18 @@
package org.springframework.security.util;
import org.springframework.security.intercept.web.FilterInvocation;
import org.springframework.security.intercept.web.FilterInvocationDefinitionSource;
import org.springframework.security.intercept.web.FilterChainMap;
import org.springframework.security.intercept.web.FIDSToFilterChainMapConverter;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.security.intercept.web.*;
import org.springframework.util.Assert;
import javax.servlet.*;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.util.*;
/**
@ -53,10 +41,11 @@ import javax.servlet.ServletResponse;
* #doFilter(ServletRequest, ServletResponse, FilterChain)} invocations through to each <code>Filter</code> defined
* against <code>FilterChainProxy</code>.</p>
*
* <p>As of version 2.0, <tt>FilterChainProxy</tt> is configured using a {@link FilterChainMap}. In previous
* <p>As of version 2.0, <tt>FilterChainProxy</tt> is configured using an ordered Map of path patterns to <tt>List</tt>s
* of <tt>Filter</tt> objects. In previous
* versions, a {@link FilterInvocationDefinitionSource} was used. This is now deprecated in favour of namespace-based
* configuration which provides a more robust and simplfied syntax. The <tt>FilterChainMap</tt> instance will be
* created while parsing the namespace configuration, so it doesn't require an explicit bean declaration.
* configuration which provides a more robust and simplfied syntax. The Map instance will normally be
* created while parsing the namespace configuration, so doesn't have to be set explicitly.
* Instead the &lt;filter-chain-map&gt; element should be used within the FilterChainProxy bean declaration.
* This in turn should have a list of child &lt;filter-chain&gt; elements which each define a URI pattern and the list
* of filters (as comma-separated bean names) which should be applied to requests which match the pattern.
@ -92,7 +81,7 @@ import javax.servlet.ServletResponse;
* container. As per {@link org.springframework.security.util.FilterToBeanProxy} JavaDocs, we recommend you allow the IoC
* container to manage lifecycle instead of the servlet container. By default the <code>FilterToBeanProxy</code> will
* never call this class' {@link #init(FilterConfig)} and {@link #destroy()} methods, meaning each of the filters
* defined in the FilterChainMap will not be called. If you do need your filters to be
* defined in the filter chain map will not be called. If you do need your filters to be
* initialized and destroyed, please set the <code>lifecycle</code> initialization parameter against the
* <code>FilterToBeanProxy</code> to specify servlet container lifecycle management.</p>
*
@ -111,22 +100,41 @@ public class FilterChainProxy implements Filter, InitializingBean, ApplicationCo
//~ Instance fields ================================================================================================
private ApplicationContext applicationContext;
private FilterChainMap filterChainMap;
/** Map of the original pattern Strings to filter chains */
private Map uncompiledFilterChainMap;
/** Compiled pattern version of the filter chain map */
private Map filterChainMap;
private UrlMatcher matcher = new AntUrlPathMatcher();
private FilterInvocationDefinitionSource fids;
//~ Methods ========================================================================================================
public void afterPropertiesSet() throws Exception {
// Convert the FilterDefinitionSource to a filterChainMap if set
if (fids != null) {
Assert.isNull(filterChainMap, "Set the FilterChainMap or FilterInvocationDefinitionSource but not both");
Assert.isNull(uncompiledFilterChainMap, "Set the FilterChainMap or FilterInvocationDefinitionSource but not both");
setFilterChainMap(new FIDSToFilterChainMapConverter(fids, applicationContext).getFilterChainMap());
}
Assert.notNull(filterChainMap, "A FilterChainMap must be supplied");
Assert.notNull(uncompiledFilterChainMap, "A FilterChainMap must be supplied");
}
public void init(FilterConfig filterConfig) throws ServletException {
Filter[] filters = obtainAllDefinedFilters();
for (int i = 0; i < filters.length; i++) {
if (filters[i] != null) {
if (logger.isDebugEnabled()) {
logger.debug("Initializing Filter defined in ApplicationContext: '" + filters[i].toString() + "'");
}
filters[i].init(filterConfig);
}
}
}
public void destroy() {
Filter[] filters = filterChainMap.getAllDefinedFilters();
Filter[] filters = obtainAllDefinedFilters();
for (int i = 0; i < filters.length; i++) {
if (filters[i] != null) {
@ -143,10 +151,9 @@ public class FilterChainProxy implements Filter, InitializingBean, ApplicationCo
throws IOException, ServletException {
FilterInvocation fi = new FilterInvocation(request, response, chain);
List filters = getFilters(fi.getRequestUrl());
Filter[] filters = filterChainMap.getFilters(fi.getRequestUrl());
if (filters == null || filters.length == 0) {
if (filters == null || filters.size() == 0) {
if (logger.isDebugEnabled()) {
logger.debug(fi.getRequestUrl() +
filters == null ? " has no matching filters" : " has an empty filter list");
@ -161,34 +168,54 @@ public class FilterChainProxy implements Filter, InitializingBean, ApplicationCo
virtualFilterChain.doFilter(fi.getRequest(), fi.getResponse());
}
public void init(FilterConfig filterConfig) throws ServletException {
Filter[] filters = filterChainMap.getAllDefinedFilters();
/**
* Returns the first filter chain matching the supplied URL.
* TODO: Change tests and make package protected access.
*
* @param url the request URL
* @return an ordered array of Filters defining the filter chain
*/
public List getFilters(String url) {
Iterator filterChains = filterChainMap.entrySet().iterator();
for (int i = 0; i < filters.length; i++) {
if (filters[i] != null) {
if (logger.isDebugEnabled()) {
logger.debug("Initializing Filter defined in ApplicationContext: '" + filters[i].toString() + "'");
}
while (filterChains.hasNext()) {
Map.Entry entry = (Map.Entry) filterChains.next();
Object path = entry.getKey();
filters[i].init(filterConfig);
boolean matched = matcher.pathMatchesUrl(path, url);
if (logger.isDebugEnabled()) {
logger.debug("Candidate is: '" + url + "'; pattern is " + path + "; matched=" + matched);
}
if (matched) {
return (List) entry.getValue();
}
}
return null;
}
/**
* Obtains all of the <b>unique</b><code>Filter</code> instances registered in the
* <code>FilterChainMap</code>.
* <p>This is useful in ensuring a <code>Filter</code> is not
* initialized or destroyed twice.</p>
* Obtains all of the <b>unique</b><code>Filter</code> instances registered in the map of
* filter chains.
* <p>This is useful in ensuring a <code>Filter</code> is not initialized or destroyed twice.</p>
*
* @deprecated
* @return all of the <code>Filter</code> instances in the application context which have an entry
* in the <code>FilterChainMap</code> (only one entry is included in the array for
* in the map (only one entry is included in the array for
* each <code>Filter</code> that actually exists in application context, even if a given
* <code>Filter</code> is defined multiples times by the <code>FilterChainMap</code>)
*/
protected Filter[] obtainAllDefinedFilters() {
return filterChainMap.getAllDefinedFilters();
Set allFilters = new HashSet();
Iterator it = filterChainMap.values().iterator();
while (it.hasNext()) {
allFilters.addAll((List) it.next());
}
return (Filter[]) new ArrayList(allFilters).toArray(new Filter[0]);
}
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
@ -200,15 +227,69 @@ public class FilterChainProxy implements Filter, InitializingBean, ApplicationCo
* @deprecated Use namespace configuration or call setFilterChainMap instead.
*/
public void setFilterInvocationDefinitionSource(FilterInvocationDefinitionSource fids) {
if( fids instanceof RegExpBasedFilterInvocationDefinitionMap) {
matcher = new RegexUrlPathMatcher();
}
this.fids = fids;
}
public void setFilterChainMap(FilterChainMap filterChainMap) {
this.filterChainMap = filterChainMap;
/**
* Sets the mapping of URL patterns to filter chains.
*
* The map keys should be the paths and the values should be arrays of <tt>Filter</tt> objects.
* It's VERY important that the type of map used preserves ordering - the order in which the iterator
* returns the entries must be the same as the order they were added to the map, otherwise you have no way
* of guaranteeing that the most specific patterns are returned before the more general ones. So make sure
* the Map used is an instance of <tt>LinkedHashMap</tt> or an equivalent, rather than a plain <tt>HashMap</tt>, for
* example.
*
* @param filterChainMap the map of path Strings to <tt>Filter[]</tt>s.
*/
public void setFilterChainMap(Map filterChainMap) {
uncompiledFilterChainMap = new LinkedHashMap(filterChainMap);
createCompiledMap();
}
public FilterChainMap getFilterChainMap() {
return filterChainMap;
private void createCompiledMap() {
Iterator paths = uncompiledFilterChainMap.keySet().iterator();
filterChainMap = new LinkedHashMap(uncompiledFilterChainMap.size());
while (paths.hasNext()) {
Object path = paths.next();
Assert.isInstanceOf(String.class, path, "Path pattern must be a String");
Object compiledPath = matcher.compile((String)path);
Object filters = uncompiledFilterChainMap.get(path);
Assert.isInstanceOf(List.class, filters);
// Check the contents
Iterator filterIterator = ((List)filters).iterator();
while (filterIterator.hasNext()) {
Object filter = filterIterator.next();
Assert.isInstanceOf(Filter.class, filter, "Objects in filter chain must be of type Filter. ");
}
filterChainMap.put(compiledPath, filters);
}
}
/**
* Returns a copy of the underlying filter chain map. Modifications to the map contents
* will not affect the FilterChainProxy state - to change the map call <tt>setFilterChainMap</tt>.
*
* @return the map of path pattern Strings to filter chain arrays (with ordering guaranteed).
*/
public Map getFilterChainMap() {
return new LinkedHashMap(uncompiledFilterChainMap);
}
public void setMatcher(UrlMatcher matcher) {
this.matcher = matcher;
}
public UrlMatcher getMatcher() {
return matcher;
}
//~ Inner Classes ==================================================================================================
@ -221,17 +302,17 @@ public class FilterChainProxy implements Filter, InitializingBean, ApplicationCo
*/
private static class VirtualFilterChain implements FilterChain {
private FilterInvocation fi;
private Filter[] additionalFilters;
private List additionalFilters;
private int currentPosition = 0;
public VirtualFilterChain(FilterInvocation filterInvocation, Filter[] additionalFilters) {
private VirtualFilterChain(FilterInvocation filterInvocation, List additionalFilters) {
this.fi = filterInvocation;
this.additionalFilters = additionalFilters;
}
public void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException {
if (currentPosition == additionalFilters.length) {
if (currentPosition == additionalFilters.size()) {
if (logger.isDebugEnabled()) {
logger.debug(fi.getRequestUrl()
+ " reached end of additional filter chain; proceeding with original chain");
@ -241,13 +322,15 @@ public class FilterChainProxy implements Filter, InitializingBean, ApplicationCo
} else {
currentPosition++;
Filter nextFilter = (Filter) additionalFilters.get(currentPosition - 1);
if (logger.isDebugEnabled()) {
logger.debug(fi.getRequestUrl() + " at position " + currentPosition + " of "
+ additionalFilters.length + " in additional filter chain; firing Filter: '"
+ additionalFilters[currentPosition - 1] + "'");
+ additionalFilters.size() + " in additional filter chain; firing Filter: '"
+ nextFilter + "'");
}
additionalFilters[currentPosition - 1].doFilter(request, response, this);
nextFilter.doFilter(request, response, this);
}
}
}

View File

@ -6,7 +6,6 @@ import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.security.context.HttpSessionContextIntegrationFilter;
import org.springframework.security.intercept.web.FilterChainMap;
import org.springframework.security.intercept.web.FilterSecurityInterceptor;
import org.springframework.security.ui.ExceptionTranslationFilter;
import org.springframework.security.ui.basicauth.BasicProcessingFilter;
@ -15,7 +14,8 @@ import org.springframework.security.ui.webapp.AuthenticationProcessingFilter;
import org.springframework.security.ui.webapp.DefaultLoginPageGeneratingFilter;
import org.springframework.security.util.FilterChainProxy;
import javax.servlet.Filter;
import java.util.Iterator;
import java.util.List;
/**
* @author Luke Taylor
@ -41,11 +41,9 @@ public class HttpSecurityBeanDefinitionParserTests {
FilterChainProxy filterChainProxy =
(FilterChainProxy) appContext.getBean(HttpSecurityBeanDefinitionParser.DEFAULT_FILTER_CHAIN_PROXY_ID);
FilterChainMap filterChainMap = filterChainProxy.getFilterChainMap();
List filters = filterChainProxy.getFilters("/unprotected");
Filter[] filters = filterChainMap.getFilters("/unprotected");
assertTrue(filters.length == 0);
assertTrue(filters.size() == 0);
}
@Test
@ -53,20 +51,18 @@ public class HttpSecurityBeanDefinitionParserTests {
FilterChainProxy filterChainProxy =
(FilterChainProxy) appContext.getBean(HttpSecurityBeanDefinitionParser.DEFAULT_FILTER_CHAIN_PROXY_ID);
FilterChainMap filterChainMap = filterChainProxy.getFilterChainMap();
Filter[] filters = filterChainMap.getFilters("/someurl");
List filterList = filterChainProxy.getFilters("/someurl");
assertTrue("Expected 7 filters in chain", filters.length == 7);
assertTrue("Expected 7 filterList in chain", filterList.size() == 7);
assertTrue(filters[0] instanceof HttpSessionContextIntegrationFilter);
assertTrue(filters[1] instanceof LogoutFilter);
assertTrue(filters[2] instanceof AuthenticationProcessingFilter);
assertTrue(filters[3] instanceof DefaultLoginPageGeneratingFilter);
assertTrue(filters[4] instanceof BasicProcessingFilter);
assertTrue(filters[5] instanceof ExceptionTranslationFilter);
assertTrue(filters[6] instanceof FilterSecurityInterceptor);
Iterator filters = filterList.iterator();
assertTrue(filters.next() instanceof HttpSessionContextIntegrationFilter);
assertTrue(filters.next() instanceof LogoutFilter);
assertTrue(filters.next() instanceof AuthenticationProcessingFilter);
assertTrue(filters.next() instanceof DefaultLoginPageGeneratingFilter);
assertTrue(filters.next() instanceof BasicProcessingFilter);
assertTrue(filters.next() instanceof ExceptionTranslationFilter);
assertTrue(filters.next() instanceof FilterSecurityInterceptor);
}
}

View File

@ -15,10 +15,7 @@
package org.springframework.security.util;
import org.junit.After;
import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
@ -26,9 +23,17 @@ import org.springframework.security.ConfigAttribute;
import org.springframework.security.ConfigAttributeDefinition;
import org.springframework.security.MockApplicationContext;
import org.springframework.security.MockFilterConfig;
import org.springframework.security.context.HttpSessionContextIntegrationFilter;
import org.springframework.security.intercept.web.MockFilterInvocationDefinitionSource;
import org.springframework.security.intercept.web.PathBasedFilterInvocationDefinitionMap;
import org.springframework.security.ui.webapp.AuthenticationProcessingFilter;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
import java.util.List;
/**
* Tests {@link FilterChainProxy}.
@ -123,17 +128,50 @@ public class FilterChainProxyTests {
@Test
public void normalOperation() throws Exception {
doNormalOperation((FilterChainProxy) appCtx.getBean("filterChain", FilterChainProxy.class));
FilterChainProxy filterChainProxy = (FilterChainProxy) appCtx.getBean("filterChain", FilterChainProxy.class);
doNormalOperation(filterChainProxy);
}
@Test
public void normalOperationWithNewConfig() throws Exception {
doNormalOperation((FilterChainProxy) appCtx.getBean("newFilterChainProxy", FilterChainProxy.class));
FilterChainProxy filterChainProxy = (FilterChainProxy) appCtx.getBean("newFilterChainProxy", FilterChainProxy.class);
checkPathAndFilterOrder(filterChainProxy);
doNormalOperation(filterChainProxy);
}
@Test
public void normalOperationWithNewConfigRegex() throws Exception {
doNormalOperation((FilterChainProxy) appCtx.getBean("newFilterChainProxyRegex", FilterChainProxy.class));
FilterChainProxy filterChainProxy = (FilterChainProxy) appCtx.getBean("newFilterChainProxyRegex", FilterChainProxy.class);
checkPathAndFilterOrder(filterChainProxy);
doNormalOperation(filterChainProxy);
}
@Test
public void normalOperationWithNewConfigNonNamespace() throws Exception {
FilterChainProxy filterChainProxy = (FilterChainProxy) appCtx.getBean("newFilterChainProxyNonNamespace", FilterChainProxy.class);
checkPathAndFilterOrder(filterChainProxy);
doNormalOperation(filterChainProxy);
}
private void checkPathAndFilterOrder(FilterChainProxy filterChainProxy) throws Exception {
List filters = filterChainProxy.getFilters("/foo/blah");
assertEquals(1, filters.size());
assertTrue(filters.get(0) instanceof MockFilter);
filters = filterChainProxy.getFilters("/some/other/path/blah");
assertEquals(3, filters.size());
assertTrue(filters.get(0) instanceof HttpSessionContextIntegrationFilter);
assertTrue(filters.get(1) instanceof MockFilter);
assertTrue(filters.get(2) instanceof MockFilter);
filters = filterChainProxy.getFilters("/do/not/filter");
assertEquals(0, filters.size());
filters = filterChainProxy.getFilters("/another/nonspecificmatch");
assertEquals(3, filters.size());
assertTrue(filters.get(0) instanceof HttpSessionContextIntegrationFilter);
assertTrue(filters.get(1) instanceof AuthenticationProcessingFilter);
assertTrue(filters.get(2) instanceof MockFilter);
}
private void doNormalOperation(FilterChainProxy filterChainProxy) throws Exception {
@ -165,7 +203,6 @@ public class FilterChainProxyTests {
assertTrue(filter.isWasInitialized());
assertTrue(filter.isWasDoFiltered());
assertTrue(filter.isWasDestroyed());
}
//~ Inner Classes ==================================================================================================

View File

@ -28,7 +28,18 @@ http://www.springframework.org/schema/security http://www.springframework.org/sc
<bean id="mockFilter2" class="org.springframework.security.util.MockFilter"/>
<bean id="mockNotAFilter" class="org.springframework.security.util.MockNotAFilter"/>
<!-- These are just here so we have filters of a specfic type to check the ordering is as expected -->
<bean id="sif" class="org.springframework.security.context.HttpSessionContextIntegrationFilter"/>
<bean id="apf" class="org.springframework.security.ui.webapp.AuthenticationProcessingFilter">
<property name="defaultTargetUrl" value="/whocares"/>
<property name="authenticationFailureUrl" value="/whocares2"/>
<property name="authenticationManager">
<bean class="org.springframework.security.MockAuthenticationManager"/>
</property>
</bean>
<bean id="mockNotAFilter" class="org.springframework.security.util.MockNotAFilter"/>
<bean id="filterChain" class="org.springframework.security.util.FilterChainProxy">
<property name="filterInvocationDefinitionSource">
@ -47,6 +58,7 @@ http://www.springframework.org/schema/security http://www.springframework.org/sc
<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>
</bean>
@ -55,9 +67,40 @@ http://www.springframework.org/schema/security http://www.springframework.org/sc
<sec:filter-chain pattern="\A/foo/.*\Z" filters="mockFilter"/>
<sec:filter-chain pattern="\A/some/other/path/.*\Z" filters="sif,mockFilter,mockFilter2"/>
<sec:filter-chain pattern="\A/do/not/filter\Z" filters="none"/>
<sec:filter-chain pattern="\A/.*\Z" filters="sif,apf,mockFilter"/>
</sec:filter-chain-map>
</bean>
<bean id="sif" class="org.springframework.security.context.HttpSessionContextIntegrationFilter"/>
<bean id="newFilterChainProxyNonNamespace" class="org.springframework.security.util.FilterChainProxy">
<property name="matcher">
<bean class="org.springframework.security.util.AntUrlPathMatcher"/>
</property>
<property name="filterChainMap">
<map>
<entry key="/foo/**">
<list>
<ref local="mockFilter"/>
</list>
</entry>
<entry key="/some/other/path/**">
<list>
<ref local="sif"/>
<ref local="mockFilter"/>
<ref local="mockFilter2"/>
</list>
</entry>
<entry key="/do/not/filter">
<list/>
</entry>
<entry key="/**">
<list>
<ref local="sif"/>
<ref local="apf"/>
<ref local="mockFilter"/>
</list>
</entry>
</map>
</property>
</bean>
</beans>