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:
Luke Taylor 2007-10-20 23:05:03 +00:00
parent d6fe97de43
commit 9b8c06e9f6
11 changed files with 567 additions and 152 deletions

View File

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

View File

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

View File

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

View File

@ -131,6 +131,10 @@ public class PathBasedFilterInvocationDefinitionMap extends AbstractFilterInvoca
this.convertUrlToLowercaseBeforeComparison = convertUrlToLowercaseBeforeComparison;
}
List getRequestMap() {
return requestMap;
}
//~ Inner Classes ==================================================================================================
protected class EntryHolder {

View File

@ -118,6 +118,10 @@ public class RegExpBasedFilterInvocationDefinitionMap extends AbstractFilterInvo
this.convertUrlToLowercaseBeforeComparison = convertUrlToLowercaseBeforeComparison;
}
List getRequestMap() {
return requestMap;
}
//~ Inner Classes ==================================================================================================
protected class EntryHolder {

View File

@ -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 "/**";
}
}

View File

@ -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>&lt;security:http /&gt</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 &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.
* An example configuration might look like this:
*
* <pre>
&lt;bean id="myfilterChainProxy" class="org.springframework.security.util.FilterChainProxy">
&lt;security:filter-chain-map pathType="ant">
&lt;security:filter-chain pattern="/do/not/filter" filters="none"/>
&lt;security:filter-chain pattern="/**" filters="filter1,filter2,filter3"/>
&lt;/security:filter-chain-map>
&lt;/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
}
}
}
}

View File

@ -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 "/.*";
}
}

View File

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

View File

@ -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 ==================================================================================================

View File

@ -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>