SEC-568: Introduced FilterChainMap as a simpler option for configuring FilterChainProxy and introduced a namespace-based for configuring it. The Url pattern matching is factored out into a separate strategy with ant and regex versions.
This commit is contained in:
parent
d6fe97de43
commit
9b8c06e9f6
|
@ -0,0 +1,110 @@
|
|||
package org.springframework.security.config;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.config.BeanDefinitionHolder;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.beans.factory.xml.BeanDefinitionDecorator;
|
||||
import org.springframework.beans.factory.xml.ParserContext;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.security.intercept.web.FilterChainMap;
|
||||
import org.springframework.security.util.RegexUrlPathMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Sets the FilterChainMap for a FilterChainProxy bean declaration.
|
||||
*
|
||||
* @author Luke Taylor
|
||||
* @version $Id$
|
||||
*/
|
||||
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();
|
||||
Element elt = (Element)node;
|
||||
|
||||
String pathType = elt.getAttribute(HttpSecurityBeanDefinitionParser.PATTERN_TYPE_ATTRIBUTE);
|
||||
|
||||
if (HttpSecurityBeanDefinitionParser.PATTERN_TYPE_REGEX.equals(pathType)) {
|
||||
filterChainMap.setUrlPathMatcher(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");
|
||||
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);
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
package org.springframework.security.intercept.web;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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).
|
||||
*
|
||||
* @author Luke Taylor
|
||||
* @version $Id$
|
||||
*/
|
||||
public class FIDSToFilterChainMapConverter {
|
||||
|
||||
private FilterChainMap filterChainMap = new FilterChainMap();
|
||||
|
||||
public FIDSToFilterChainMapConverter(FilterInvocationDefinitionSource fids, ApplicationContext appContext) {
|
||||
|
||||
List requestMap;
|
||||
|
||||
// TODO: Check if this is necessary. Retained from refactoring of FilterChainProxy
|
||||
if (fids.getConfigAttributeDefinitions() == null) {
|
||||
throw new IllegalArgumentException("FilterChainProxy requires the FilterInvocationDefinitionSource to " +
|
||||
"return a non-null response to getConfigAttributeDefinitions()");
|
||||
}
|
||||
|
||||
if (fids instanceof PathBasedFilterInvocationDefinitionMap) {
|
||||
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());
|
||||
}
|
||||
|
||||
Iterator entries = requestMap.iterator();
|
||||
|
||||
while (entries.hasNext()) {
|
||||
Object entry = entries.next();
|
||||
String path;
|
||||
ConfigAttributeDefinition configAttributeDefinition;
|
||||
|
||||
if (entry instanceof PathBasedFilterInvocationDefinitionMap.EntryHolder) {
|
||||
path = ((PathBasedFilterInvocationDefinitionMap.EntryHolder)entry).getAntPath();
|
||||
configAttributeDefinition = ((PathBasedFilterInvocationDefinitionMap.EntryHolder)entry).getConfigAttributeDefinition();
|
||||
} else {
|
||||
path = ((RegExpBasedFilterInvocationDefinitionMap.EntryHolder)entry).getCompiledPattern().pattern();
|
||||
configAttributeDefinition = ((RegExpBasedFilterInvocationDefinitionMap.EntryHolder)entry).getConfigAttributeDefinition();
|
||||
}
|
||||
|
||||
List filters = new ArrayList();
|
||||
|
||||
Iterator attributes = configAttributeDefinition.getConfigAttributes();
|
||||
|
||||
while (attributes.hasNext()) {
|
||||
ConfigAttribute attr = (ConfigAttribute) attributes.next();
|
||||
String filterName = attr.getAttribute();
|
||||
|
||||
if (filterName == null) {
|
||||
throw new IllegalArgumentException("Configuration attribute: '" + attr
|
||||
+ "' returned null to the getAttribute() method, which is invalid when used with FilterChainProxy");
|
||||
}
|
||||
|
||||
if (!filterName.equals(FilterChainProxy.TOKEN_NONE)) {
|
||||
filters.add(appContext.getBean(filterName, Filter.class));
|
||||
}
|
||||
}
|
||||
|
||||
filterChainMap.addSecureUrl(path, (Filter[]) filters.toArray(new Filter[filters.size()]));
|
||||
}
|
||||
}
|
||||
|
||||
public FilterChainMap getFilterChainMap() {
|
||||
return filterChainMap;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
package org.springframework.security.intercept.web;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.security.util.AntUrlPathMatcher;
|
||||
import org.springframework.security.util.UrlMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Maps filter invocations to filter chains. Used to configure FilterChainProxy.
|
||||
*
|
||||
* @see org.springframework.security.util.FilterChainProxy
|
||||
*
|
||||
* @author luke
|
||||
* @version $Id$
|
||||
* @since 2.0
|
||||
*/
|
||||
public class FilterChainMap implements InitializingBean {
|
||||
private static final Log logger = LogFactory.getLog(FilterChainMap.class);
|
||||
|
||||
private List paths = new ArrayList();
|
||||
private List compiledPaths = new ArrayList();
|
||||
private List filterChains = new ArrayList();
|
||||
|
||||
private UrlMatcher matcher = new AntUrlPathMatcher();
|
||||
|
||||
public FilterChainMap() {
|
||||
}
|
||||
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
Assert.notEmpty(paths, "No secure URL paths defined");
|
||||
}
|
||||
|
||||
public void addSecureUrl(String path, Filter[] filters) {
|
||||
Assert.hasText(path, "The Path must not be empty or null");
|
||||
Assert.notNull(filters, "The Filter array must not be null");
|
||||
paths.add(path);
|
||||
compiledPaths.add(matcher.compile(path));
|
||||
filterChains.add(filters);
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Added pattern: " + path + "; filters: " + Arrays.asList(filters));
|
||||
}
|
||||
}
|
||||
|
||||
public void setUrlPathMatcher(UrlMatcher matcher) {
|
||||
this.matcher = matcher;
|
||||
}
|
||||
|
||||
public UrlMatcher getMatcher() {
|
||||
return matcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first filter chain matching the supplied URL.
|
||||
*
|
||||
* @param url the request URL
|
||||
* @return an ordered array of Filters defining the filter chain
|
||||
*/
|
||||
public Filter[] getFilters(String url) {
|
||||
|
||||
for (int i=0; i < compiledPaths.size(); i++) {
|
||||
Object path = compiledPaths.get(i);
|
||||
|
||||
boolean matched = matcher.pathMatchesUrl(path, url);
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Candidate is: '" + url + "'; pattern is " + paths.get(i) + "; matched=" + matched);
|
||||
}
|
||||
|
||||
if (matched) {
|
||||
return (Filter[]) filterChains.get(i);
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
||||
* @return all of the <code>Filter</code> instances which have an entry
|
||||
* in the <code>FilterChainMap</code> (only one entry is included in the array for
|
||||
* each <code>Filter</code> instance, even if a given
|
||||
* <code>Filter</code> is used multiples times by the <code>FilterChainMap</code>)
|
||||
*/
|
||||
public Filter[] getAllDefinedFilters() {
|
||||
Set allFilters = new HashSet();
|
||||
|
||||
Iterator it = filterChains.iterator();
|
||||
while (it.hasNext()) {
|
||||
Filter[] filterChain = (Filter[])it.next();
|
||||
|
||||
for(int i=0; i < filterChain.length; i++) {
|
||||
allFilters.add(filterChain[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return (Filter[]) new ArrayList(allFilters).toArray(new Filter[0]);
|
||||
}
|
||||
}
|
|
@ -131,6 +131,10 @@ public class PathBasedFilterInvocationDefinitionMap extends AbstractFilterInvoca
|
|||
this.convertUrlToLowercaseBeforeComparison = convertUrlToLowercaseBeforeComparison;
|
||||
}
|
||||
|
||||
List getRequestMap() {
|
||||
return requestMap;
|
||||
}
|
||||
|
||||
//~ Inner Classes ==================================================================================================
|
||||
|
||||
protected class EntryHolder {
|
||||
|
|
|
@ -118,6 +118,10 @@ public class RegExpBasedFilterInvocationDefinitionMap extends AbstractFilterInvo
|
|||
this.convertUrlToLowercaseBeforeComparison = convertUrlToLowercaseBeforeComparison;
|
||||
}
|
||||
|
||||
List getRequestMap() {
|
||||
return requestMap;
|
||||
}
|
||||
|
||||
//~ Inner Classes ==================================================================================================
|
||||
|
||||
protected class EntryHolder {
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
package org.springframework.security.util;
|
||||
|
||||
import org.springframework.util.PathMatcher;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
/**
|
||||
* Ant path strategy for URL matching.
|
||||
*
|
||||
* @author luke
|
||||
* @version $Id$
|
||||
*/
|
||||
public class AntUrlPathMatcher implements UrlMatcher {
|
||||
private static final Log logger = LogFactory.getLog(AntUrlPathMatcher.class);
|
||||
|
||||
private boolean convertToLowercaseBeforeComparison = true;
|
||||
private PathMatcher pathMatcher = new AntPathMatcher();
|
||||
|
||||
public Object compile(String path) {
|
||||
if (convertToLowercaseBeforeComparison) {
|
||||
return path.toLowerCase();
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
public void setConvertToLowercaseBeforeComparison(boolean convertToLowercaseBeforeComparison) {
|
||||
this.convertToLowercaseBeforeComparison = convertToLowercaseBeforeComparison;
|
||||
}
|
||||
|
||||
public boolean pathMatchesUrl(Object path, String url) {
|
||||
if (convertToLowercaseBeforeComparison) {
|
||||
url = url.toLowerCase();
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Converted URL to lowercase, from: '" + url + "'; to: '" + url + "'");
|
||||
}
|
||||
}
|
||||
|
||||
return pathMatcher.match((String)path, url);
|
||||
}
|
||||
|
||||
public String getUniversalMatchPattern() {
|
||||
return "/**";
|
||||
}
|
||||
}
|
|
@ -15,11 +15,10 @@
|
|||
|
||||
package org.springframework.security.util;
|
||||
|
||||
import org.springframework.security.ConfigAttribute;
|
||||
import org.springframework.security.ConfigAttributeDefinition;
|
||||
|
||||
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;
|
||||
|
@ -34,12 +33,6 @@ import org.springframework.util.Assert;
|
|||
|
||||
import java.io.IOException;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.Vector;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.FilterConfig;
|
||||
|
@ -49,22 +42,48 @@ import javax.servlet.ServletResponse;
|
|||
|
||||
|
||||
/**
|
||||
* Delegates <code>Filter</code> requests to a list of Spring-managed beans.<p>The <code>FilterChainProxy</code> is
|
||||
* loaded via a standard {@link org.springframework.security.util.FilterToBeanProxy} declaration in <code>web.xml</code>.
|
||||
* Delegates <code>Filter</code> requests to a list of Spring-managed beans.
|
||||
* As of version 2.0, you shouldn't need to explicitly configure a <tt>FilterChainProxy</tt> bean in your application
|
||||
* context unless you need very fine control over the filter chain contents. Most cases should be adequately covered
|
||||
* by the default <tt><security:http /></tt> namespace configuration options.
|
||||
*
|
||||
* <p>The <code>FilterChainProxy</code> is loaded via a standard
|
||||
* {@link org.springframework.security.util.FilterToBeanProxy} declaration in <code>web.xml</code>.
|
||||
* <code>FilterChainProxy</code> will then pass {@link #init(FilterConfig)}, {@link #destroy()} and {@link
|
||||
* #doFilter(ServletRequest, ServletResponse, FilterChain)} invocations through to each <code>Filter</code> defined
|
||||
* against <code>FilterChainProxy</code>.</p>
|
||||
* <p><code>FilterChainProxy</code> is configured using a standard {@link
|
||||
* org.springframework.security.intercept.web.FilterInvocationDefinitionSource}. Each possible URI pattern that
|
||||
* <code>FilterChainProxy</code> should service must be entered. The first matching URI pattern located by
|
||||
* <code>FilterInvocationDefinitionSource</code> for a given request will be used to define all of the
|
||||
*
|
||||
* <p>As of version 2.0, <tt>FilterChainProxy</tt> is configured using a {@link FilterChainMap}. 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.
|
||||
* Instead the <filter-chain-map> element should be used within the FilterChainProxy bean declaration.
|
||||
* This in turn should have a list of child <filter-chain> 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.
|
||||
* An example configuration might look like this:
|
||||
*
|
||||
* <pre>
|
||||
<bean id="myfilterChainProxy" class="org.springframework.security.util.FilterChainProxy">
|
||||
<security:filter-chain-map pathType="ant">
|
||||
<security:filter-chain pattern="/do/not/filter" filters="none"/>
|
||||
<security:filter-chain pattern="/**" filters="filter1,filter2,filter3"/>
|
||||
</security:filter-chain-map>
|
||||
</bean>
|
||||
* </pre>
|
||||
*
|
||||
* The names "filter1", "filter2", "filter3" should be the bean names of <tt>Filter</tt> instances defined in the
|
||||
* application context. The order of the names defines the order in which the filters will be applied. As shown above,
|
||||
* use of the value "none" for the "filters" can be used to exclude
|
||||
* Please consult the security namespace schema file for a full list of available configuration options.
|
||||
* </p>
|
||||
*
|
||||
*<p>
|
||||
* Each possible URI pattern that <code>FilterChainProxy</code> should service must be entered.
|
||||
* The first matching URI pattern for a given request will be used to define all of the
|
||||
* <code>Filter</code>s that apply to that request. NB: This means you must put most specific URI patterns at the top
|
||||
* of the list, and ensure all <code>Filter</code>s that should apply for a given URI pattern are entered against the
|
||||
* respective entry. The <code>FilterChainProxy</code> will not iterate the remainder of the URI patterns to locate
|
||||
* additional <code>Filter</code>s. The <code>FilterInvocationDefinitionSource</code> described the applicable URI
|
||||
* pattern to fire the filter chain, followed by a list of configuration attributes. Each configuration attribute's
|
||||
* {@link org.springframework.security.ConfigAttribute#getAttribute()} corresponds to a bean name that is available from the
|
||||
* application context.</p>
|
||||
* additional <code>Filter</code>s.</p>
|
||||
* <p><code>FilterChainProxy</code> respects normal handling of <code>Filter</code>s that elect not to call {@link
|
||||
* javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse,
|
||||
* javax.servlet.FilterChain)}, in that the remainder of the origial or <code>FilterChainProxy</code>-declared filter
|
||||
|
@ -73,14 +92,14 @@ 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 against <code>FilterInvocationDefinitionSource</code> will not be called. If you do need your filters to be
|
||||
* defined in the FilterChainMap 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>
|
||||
* <p>If a filter name of {@link #TOKEN_NONE} is used, this allows specification of a filter pattern which should
|
||||
* never cause any filters to fire.</p>
|
||||
*
|
||||
* @author Carlos Sanchez
|
||||
* @author Ben Alex
|
||||
* @author Luke Taylor
|
||||
*
|
||||
* @version $Id$
|
||||
*/
|
||||
public class FilterChainProxy implements Filter, InitializingBean, ApplicationContextAware {
|
||||
|
@ -92,19 +111,22 @@ public class FilterChainProxy implements Filter, InitializingBean, ApplicationCo
|
|||
//~ Instance fields ================================================================================================
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
private FilterInvocationDefinitionSource filterInvocationDefinitionSource;
|
||||
|
||||
private FilterChainMap filterChainMap;
|
||||
private FilterInvocationDefinitionSource fids;
|
||||
//~ Methods ========================================================================================================
|
||||
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
Assert.notNull(filterInvocationDefinitionSource, "filterInvocationDefinitionSource must be specified");
|
||||
Assert.notNull(this.filterInvocationDefinitionSource.getConfigAttributeDefinitions(),
|
||||
"FilterChainProxy requires the FilterInvocationDefinitionSource to return a non-null response to "
|
||||
+ "getConfigAttributeDefinitions()");
|
||||
// Convert the FilterDefinitionSource to a filterChainMap if set
|
||||
if (fids != null) {
|
||||
Assert.isNull(filterChainMap, "Set the FilterChainMap or FilterInvocationDefinitionSource but not both");
|
||||
setFilterChainMap(new FIDSToFilterChainMapConverter(fids, applicationContext).getFilterChainMap());
|
||||
}
|
||||
|
||||
Assert.notNull(filterChainMap, "A FilterChainMap must be supplied");
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
Filter[] filters = obtainAllDefinedFilters();
|
||||
Filter[] filters = filterChainMap.getAllDefinedFilters();
|
||||
|
||||
for (int i = 0; i < filters.length; i++) {
|
||||
if (filters[i] != null) {
|
||||
|
@ -118,26 +140,16 @@ public class FilterChainProxy implements Filter, InitializingBean, ApplicationCo
|
|||
}
|
||||
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
|
||||
throws IOException, ServletException {
|
||||
throws IOException, ServletException {
|
||||
|
||||
FilterInvocation fi = new FilterInvocation(request, response, chain);
|
||||
|
||||
ConfigAttributeDefinition cad = this.filterInvocationDefinitionSource.getAttributes(fi);
|
||||
Filter[] filters = filterChainMap.getFilters(fi.getRequestUrl());
|
||||
|
||||
if (cad == null) {
|
||||
if (filters == null || filters.length == 0) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(fi.getRequestUrl() + " has no matching filters");
|
||||
}
|
||||
|
||||
chain.doFilter(request, response);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Filter[] filters = obtainAllDefinedFilters(cad);
|
||||
|
||||
if (filters.length == 0) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(fi.getRequestUrl() + " has an empty filter list");
|
||||
logger.debug(fi.getRequestUrl() +
|
||||
filters == null ? " has no matching filters" : " has an empty filter list");
|
||||
}
|
||||
|
||||
chain.doFilter(request, response);
|
||||
|
@ -149,12 +161,8 @@ public class FilterChainProxy implements Filter, InitializingBean, ApplicationCo
|
|||
virtualFilterChain.doFilter(fi.getRequest(), fi.getResponse());
|
||||
}
|
||||
|
||||
public FilterInvocationDefinitionSource getFilterInvocationDefinitionSource() {
|
||||
return filterInvocationDefinitionSource;
|
||||
}
|
||||
|
||||
public void init(FilterConfig filterConfig) throws ServletException {
|
||||
Filter[] filters = obtainAllDefinedFilters();
|
||||
Filter[] filters = filterChainMap.getAllDefinedFilters();
|
||||
|
||||
for (int i = 0; i < filters.length; i++) {
|
||||
if (filters[i] != null) {
|
||||
|
@ -168,70 +176,39 @@ public class FilterChainProxy implements Filter, InitializingBean, ApplicationCo
|
|||
}
|
||||
|
||||
/**
|
||||
* Obtains all of the <b>unique</b><code>Filter</code> instances registered against the
|
||||
* <code>FilterInvocationDefinitionSource</code>.<p>This is useful in ensuring a <code>Filter</code> is not
|
||||
* 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>
|
||||
*
|
||||
* @return all of the <code>Filter</code> instances in the application context for which there has been an entry
|
||||
* against the <code>FilterInvocationDefinitionSource</code> (only one entry is included in the array for
|
||||
* @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
|
||||
* each <code>Filter</code> that actually exists in application context, even if a given
|
||||
* <code>Filter</code> is defined multiples times by the <code>FilterInvocationDefinitionSource</code>)
|
||||
* <code>Filter</code> is defined multiples times by the <code>FilterChainMap</code>)
|
||||
*/
|
||||
protected Filter[] obtainAllDefinedFilters() {
|
||||
Iterator cads = this.filterInvocationDefinitionSource.getConfigAttributeDefinitions();
|
||||
Set list = new LinkedHashSet();
|
||||
|
||||
while (cads.hasNext()) {
|
||||
ConfigAttributeDefinition attribDef = (ConfigAttributeDefinition) cads.next();
|
||||
Filter[] filters = obtainAllDefinedFilters(attribDef);
|
||||
|
||||
for (int i = 0; i < filters.length; i++) {
|
||||
list.add(filters[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return (Filter[]) list.toArray(new Filter[0]);
|
||||
return filterChainMap.getAllDefinedFilters();
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains all of the <code>Filter</code> instances registered against the specified
|
||||
* <code>ConfigAttributeDefinition</code>.
|
||||
*
|
||||
* @param configAttributeDefinition for which we want to obtain associated <code>Filter</code>s
|
||||
*
|
||||
* @return the <code>Filter</code>s against the specified <code>ConfigAttributeDefinition</code> (never
|
||||
* <code>null</code>)
|
||||
*
|
||||
* @throws IllegalArgumentException DOCUMENT ME!
|
||||
*/
|
||||
private Filter[] obtainAllDefinedFilters(ConfigAttributeDefinition configAttributeDefinition) {
|
||||
List list = new Vector();
|
||||
Iterator attributes = configAttributeDefinition.getConfigAttributes();
|
||||
|
||||
while (attributes.hasNext()) {
|
||||
ConfigAttribute attr = (ConfigAttribute) attributes.next();
|
||||
String filterName = attr.getAttribute();
|
||||
|
||||
if (filterName == null) {
|
||||
throw new IllegalArgumentException("Configuration attribute: '" + attr
|
||||
+ "' returned null to the getAttribute() method, which is invalid when used with FilterChainProxy");
|
||||
}
|
||||
|
||||
if (!filterName.equals(TOKEN_NONE)) {
|
||||
list.add(this.applicationContext.getBean(filterName, Filter.class));
|
||||
}
|
||||
}
|
||||
|
||||
return (Filter[]) list.toArray(new Filter[list.size()]);
|
||||
}
|
||||
|
||||
public void setApplicationContext(ApplicationContext applicationContext)
|
||||
throws BeansException {
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
public void setFilterInvocationDefinitionSource(FilterInvocationDefinitionSource filterInvocationDefinitionSource) {
|
||||
this.filterInvocationDefinitionSource = filterInvocationDefinitionSource;
|
||||
/**
|
||||
*
|
||||
* @deprecated Use namespace configuration or call setFilterChainMap instead.
|
||||
*/
|
||||
public void setFilterInvocationDefinitionSource(FilterInvocationDefinitionSource fids) {
|
||||
this.fids = fids;
|
||||
}
|
||||
|
||||
public void setFilterChainMap(FilterChainMap filterChainMap) {
|
||||
this.filterChainMap = filterChainMap;
|
||||
}
|
||||
|
||||
public FilterChainMap getFilterChainMap() {
|
||||
return filterChainMap;
|
||||
}
|
||||
|
||||
//~ Inner Classes ==================================================================================================
|
||||
|
@ -242,7 +219,7 @@ public class FilterChainProxy implements Filter, InitializingBean, ApplicationCo
|
|||
* <code>FilterChain</code> is used by <code>FilterChainProxy</code> to determine if the next <code>Filter</code>
|
||||
* should be called or not.</p>
|
||||
*/
|
||||
private class VirtualFilterChain implements FilterChain {
|
||||
private static class VirtualFilterChain implements FilterChain {
|
||||
private FilterInvocation fi;
|
||||
private Filter[] additionalFilters;
|
||||
private int currentPosition = 0;
|
||||
|
@ -252,8 +229,6 @@ public class FilterChainProxy implements Filter, InitializingBean, ApplicationCo
|
|||
this.additionalFilters = additionalFilters;
|
||||
}
|
||||
|
||||
private VirtualFilterChain() {}
|
||||
|
||||
public void doFilter(ServletRequest request, ServletResponse response)
|
||||
throws IOException, ServletException {
|
||||
if (currentPosition == additionalFilters.length) {
|
||||
|
@ -276,4 +251,5 @@ public class FilterChainProxy implements Filter, InitializingBean, ApplicationCo
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
package org.springframework.security.util;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @author luke
|
||||
* @version $Id$
|
||||
*/
|
||||
public class RegexUrlPathMatcher implements UrlMatcher {
|
||||
private static final Log logger = LogFactory.getLog(RegexUrlPathMatcher.class);
|
||||
|
||||
private boolean convertUrlToLowercaseBeforeComparison = true;
|
||||
|
||||
public Object compile(String path) {
|
||||
return Pattern.compile(path);
|
||||
}
|
||||
|
||||
public void setConvertUrlToLowercaseBeforeComparison(boolean convertUrlToLowercaseBeforeComparison) {
|
||||
this.convertUrlToLowercaseBeforeComparison = convertUrlToLowercaseBeforeComparison;
|
||||
}
|
||||
|
||||
public boolean pathMatchesUrl(Object compiledPath, String url) {
|
||||
Pattern pattern = (Pattern)compiledPath;
|
||||
|
||||
if (convertUrlToLowercaseBeforeComparison) {
|
||||
url = url.toLowerCase();
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Converted URL to lowercase, from: '" + url + "'; to: '" + url + "'");
|
||||
}
|
||||
}
|
||||
|
||||
return pattern.matcher(url).matches();
|
||||
}
|
||||
|
||||
public String getUniversalMatchPattern() {
|
||||
return "/.*";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package org.springframework.security.util;
|
||||
|
||||
/**
|
||||
* Strategy for deciding whether configured path matches a submitted candidate URL.
|
||||
*
|
||||
* @author luke
|
||||
* @version $Id$
|
||||
* @since 2.0
|
||||
*/
|
||||
public interface UrlMatcher {
|
||||
|
||||
Object compile(String urlPattern);
|
||||
|
||||
boolean pathMatchesUrl(Object compiledUrlPattern, String url);
|
||||
|
||||
/** Returns the path which matches every URL */
|
||||
String getUniversalMatchPattern();
|
||||
}
|
|
@ -15,23 +15,20 @@
|
|||
|
||||
package org.springframework.security.util;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
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;
|
||||
import org.springframework.security.ConfigAttribute;
|
||||
import org.springframework.security.ConfigAttributeDefinition;
|
||||
import org.springframework.security.MockApplicationContext;
|
||||
import org.springframework.security.MockFilterConfig;
|
||||
|
||||
import org.springframework.security.intercept.web.FilterInvocationDefinitionSource;
|
||||
import org.springframework.security.intercept.web.MockFilterInvocationDefinitionSource;
|
||||
import org.springframework.security.intercept.web.PathBasedFilterInvocationDefinitionMap;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
|
||||
|
||||
/**
|
||||
* Tests {@link FilterChainProxy}.
|
||||
|
@ -40,32 +37,30 @@ import org.springframework.mock.web.MockHttpServletResponse;
|
|||
* @author Ben Alex
|
||||
* @version $Id$
|
||||
*/
|
||||
public class FilterChainProxyTests extends TestCase {
|
||||
//~ Constructors ===================================================================================================
|
||||
|
||||
// ===========================================================
|
||||
public FilterChainProxyTests() {
|
||||
super();
|
||||
}
|
||||
|
||||
public FilterChainProxyTests(String arg0) {
|
||||
super(arg0);
|
||||
}
|
||||
public class FilterChainProxyTests {
|
||||
private ClassPathXmlApplicationContext appCtx;
|
||||
|
||||
//~ Methods ========================================================================================================
|
||||
|
||||
// ================================================================
|
||||
public static void main(String[] args) {
|
||||
junit.textui.TestRunner.run(FilterChainProxyTests.class);
|
||||
@Before
|
||||
public void loadContext() {
|
||||
appCtx = new ClassPathXmlApplicationContext("org/springframework/security/util/filtertest-valid.xml");
|
||||
}
|
||||
|
||||
public void testDetectsFilterInvocationDefinitionSourceThatDoesNotReturnAllConfigAttributes()
|
||||
throws Exception {
|
||||
@After
|
||||
public void closeContext() {
|
||||
if (appCtx != null) {
|
||||
appCtx.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDetectsFilterInvocationDefinitionSourceThatDoesNotReturnAllConfigAttributes() throws Exception {
|
||||
FilterChainProxy filterChainProxy = new FilterChainProxy();
|
||||
filterChainProxy.setApplicationContext(MockApplicationContext.getContext());
|
||||
filterChainProxy.setFilterInvocationDefinitionSource(new MockFilterInvocationDefinitionSource(false, false));
|
||||
|
||||
try {
|
||||
filterChainProxy.setFilterInvocationDefinitionSource(new MockFilterInvocationDefinitionSource(false, false));
|
||||
filterChainProxy.afterPropertiesSet();
|
||||
fail("Should have thrown IllegalArgumentException");
|
||||
} catch (IllegalArgumentException expected) {
|
||||
|
@ -74,8 +69,8 @@ public class FilterChainProxyTests extends TestCase {
|
|||
}
|
||||
}
|
||||
|
||||
public void testDetectsIfConfigAttributeDoesNotReturnValueForGetAttributeMethod()
|
||||
throws Exception {
|
||||
@Test
|
||||
public void testDetectsIfConfigAttributeDoesNotReturnValueForGetAttributeMethod() throws Exception {
|
||||
FilterChainProxy filterChainProxy = new FilterChainProxy();
|
||||
filterChainProxy.setApplicationContext(MockApplicationContext.getContext());
|
||||
|
||||
|
@ -86,9 +81,9 @@ public class FilterChainProxyTests extends TestCase {
|
|||
fids.addSecureUrl("/**", cad);
|
||||
|
||||
filterChainProxy.setFilterInvocationDefinitionSource(fids);
|
||||
filterChainProxy.afterPropertiesSet();
|
||||
|
||||
try {
|
||||
filterChainProxy.afterPropertiesSet();
|
||||
filterChainProxy.init(new MockFilterConfig());
|
||||
fail("Should have thrown IllegalArgumentException");
|
||||
} catch (IllegalArgumentException expected) {
|
||||
|
@ -97,8 +92,8 @@ public class FilterChainProxyTests extends TestCase {
|
|||
}
|
||||
}
|
||||
|
||||
public void testDetectsMissingFilterInvocationDefinitionSource()
|
||||
throws Exception {
|
||||
@Test
|
||||
public void testDetectsMissingFilterInvocationDefinitionSource() throws Exception {
|
||||
FilterChainProxy filterChainProxy = new FilterChainProxy();
|
||||
filterChainProxy.setApplicationContext(MockApplicationContext.getContext());
|
||||
|
||||
|
@ -106,12 +101,11 @@ public class FilterChainProxyTests extends TestCase {
|
|||
filterChainProxy.afterPropertiesSet();
|
||||
fail("Should have thrown IllegalArgumentException");
|
||||
} catch (IllegalArgumentException expected) {
|
||||
assertEquals("filterInvocationDefinitionSource must be specified", expected.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoNotFilter() throws Exception {
|
||||
ApplicationContext appCtx = new ClassPathXmlApplicationContext("org/springframework/security/util/filtertest-valid.xml");
|
||||
FilterChainProxy filterChainProxy = (FilterChainProxy) appCtx.getBean("filterChain", FilterChainProxy.class);
|
||||
MockFilter filter = (MockFilter) appCtx.getBean("mockFilter", MockFilter.class);
|
||||
|
||||
|
@ -127,16 +121,22 @@ public class FilterChainProxyTests extends TestCase {
|
|||
assertFalse(filter.isWasDestroyed());
|
||||
}
|
||||
|
||||
public void testGettersSetters() {
|
||||
FilterChainProxy filterChainProxy = new FilterChainProxy();
|
||||
FilterInvocationDefinitionSource fids = new MockFilterInvocationDefinitionSource(false, false);
|
||||
filterChainProxy.setFilterInvocationDefinitionSource(fids);
|
||||
assertEquals(fids, filterChainProxy.getFilterInvocationDefinitionSource());
|
||||
@Test
|
||||
public void normalOperation() throws Exception {
|
||||
doNormalOperation((FilterChainProxy) appCtx.getBean("filterChain", FilterChainProxy.class));
|
||||
}
|
||||
|
||||
public void testNormalOperation() throws Exception {
|
||||
ApplicationContext appCtx = new ClassPathXmlApplicationContext("org/springframework/security/util/filtertest-valid.xml");
|
||||
FilterChainProxy filterChainProxy = (FilterChainProxy) appCtx.getBean("filterChain", FilterChainProxy.class);
|
||||
@Test
|
||||
public void normalOperationWithNewConfig() throws Exception {
|
||||
doNormalOperation((FilterChainProxy) appCtx.getBean("newFilterChainProxy", FilterChainProxy.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void normalOperationWithNewConfigRegex() throws Exception {
|
||||
doNormalOperation((FilterChainProxy) appCtx.getBean("newFilterChainProxyRegex", FilterChainProxy.class));
|
||||
}
|
||||
|
||||
private void doNormalOperation(FilterChainProxy filterChainProxy) throws Exception {
|
||||
MockFilter filter = (MockFilter) appCtx.getBean("mockFilter", MockFilter.class);
|
||||
assertFalse(filter.isWasInitialized());
|
||||
assertFalse(filter.isWasDoFiltered());
|
||||
|
@ -165,6 +165,7 @@ public class FilterChainProxyTests extends TestCase {
|
|||
assertTrue(filter.isWasInitialized());
|
||||
assertTrue(filter.isWasDoFiltered());
|
||||
assertTrue(filter.isWasDestroyed());
|
||||
|
||||
}
|
||||
|
||||
//~ Inner Classes ==================================================================================================
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
|
||||
|
||||
<!--
|
||||
* Copyright 2004 Acegi Technology Pty Limited
|
||||
*
|
||||
|
@ -18,8 +18,11 @@
|
|||
*
|
||||
* $Id$
|
||||
-->
|
||||
|
||||
<beans>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:sec="http://www.springframework.org/schema/security"
|
||||
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-2.0.xsd
|
||||
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-2.0.xsd">
|
||||
|
||||
<bean id="mockFilter" class="org.springframework.security.util.MockFilter"/>
|
||||
|
||||
|
@ -39,4 +42,22 @@
|
|||
</property>
|
||||
</bean>
|
||||
|
||||
<bean id="newFilterChainProxy" class="org.springframework.security.util.FilterChainProxy">
|
||||
<sec:filter-chain-map pathType="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-map>
|
||||
</bean>
|
||||
|
||||
<bean id="newFilterChainProxyRegex" class="org.springframework.security.util.FilterChainProxy">
|
||||
<sec:filter-chain-map pathType="regex">
|
||||
<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-map>
|
||||
</bean>
|
||||
|
||||
<bean id="sif" class="org.springframework.security.context.HttpSessionContextIntegrationFilter"/>
|
||||
|
||||
</beans>
|
||||
|
|
Loading…
Reference in New Issue