diff --git a/core/src/main/java/org/springframework/security/config/FilterChainMapBeanDefinitionDecorator.java b/core/src/main/java/org/springframework/security/config/FilterChainMapBeanDefinitionDecorator.java
new file mode 100644
index 0000000000..3cc7113709
--- /dev/null
+++ b/core/src/main/java/org/springframework/security/config/FilterChainMapBeanDefinitionDecorator.java
@@ -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 setApplicationContext 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);
+ }
+ }
+ }
+ }
+}
diff --git a/core/src/main/java/org/springframework/security/intercept/web/FIDSToFilterChainMapConverter.java b/core/src/main/java/org/springframework/security/intercept/web/FIDSToFilterChainMapConverter.java
new file mode 100644
index 0000000000..3a6eccb5df
--- /dev/null
+++ b/core/src/main/java/org/springframework/security/intercept/web/FIDSToFilterChainMapConverter.java
@@ -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;
+ }
+}
diff --git a/core/src/main/java/org/springframework/security/intercept/web/FilterChainMap.java b/core/src/main/java/org/springframework/security/intercept/web/FilterChainMap.java
new file mode 100644
index 0000000000..d41e6f7e2f
--- /dev/null
+++ b/core/src/main/java/org/springframework/security/intercept/web/FilterChainMap.java
@@ -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 uniqueFilter instances registered in the
+ * FilterChainMap.
+ *
This is useful in ensuring a Filter is not
+ * initialized or destroyed twice.
Filter instances which have an entry
+ * in the FilterChainMap (only one entry is included in the array for
+ * each Filter instance, even if a given
+ * Filter is used multiples times by the FilterChainMap)
+ */
+ 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]);
+ }
+}
diff --git a/core/src/main/java/org/springframework/security/intercept/web/PathBasedFilterInvocationDefinitionMap.java b/core/src/main/java/org/springframework/security/intercept/web/PathBasedFilterInvocationDefinitionMap.java
index 88c2e063fd..ea7da72e21 100644
--- a/core/src/main/java/org/springframework/security/intercept/web/PathBasedFilterInvocationDefinitionMap.java
+++ b/core/src/main/java/org/springframework/security/intercept/web/PathBasedFilterInvocationDefinitionMap.java
@@ -131,6 +131,10 @@ public class PathBasedFilterInvocationDefinitionMap extends AbstractFilterInvoca
this.convertUrlToLowercaseBeforeComparison = convertUrlToLowercaseBeforeComparison;
}
+ List getRequestMap() {
+ return requestMap;
+ }
+
//~ Inner Classes ==================================================================================================
protected class EntryHolder {
diff --git a/core/src/main/java/org/springframework/security/intercept/web/RegExpBasedFilterInvocationDefinitionMap.java b/core/src/main/java/org/springframework/security/intercept/web/RegExpBasedFilterInvocationDefinitionMap.java
index c5aacd0af7..ec08b3a005 100644
--- a/core/src/main/java/org/springframework/security/intercept/web/RegExpBasedFilterInvocationDefinitionMap.java
+++ b/core/src/main/java/org/springframework/security/intercept/web/RegExpBasedFilterInvocationDefinitionMap.java
@@ -118,6 +118,10 @@ public class RegExpBasedFilterInvocationDefinitionMap extends AbstractFilterInvo
this.convertUrlToLowercaseBeforeComparison = convertUrlToLowercaseBeforeComparison;
}
+ List getRequestMap() {
+ return requestMap;
+ }
+
//~ Inner Classes ==================================================================================================
protected class EntryHolder {
diff --git a/core/src/main/java/org/springframework/security/util/AntUrlPathMatcher.java b/core/src/main/java/org/springframework/security/util/AntUrlPathMatcher.java
new file mode 100644
index 0000000000..6ba621c28d
--- /dev/null
+++ b/core/src/main/java/org/springframework/security/util/AntUrlPathMatcher.java
@@ -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 "/**";
+ }
+}
diff --git a/core/src/main/java/org/springframework/security/util/FilterChainProxy.java b/core/src/main/java/org/springframework/security/util/FilterChainProxy.java
index f5faf1c80c..158cdd11d1 100644
--- a/core/src/main/java/org/springframework/security/util/FilterChainProxy.java
+++ b/core/src/main/java/org/springframework/security/util/FilterChainProxy.java
@@ -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 Filter requests to a list of Spring-managed beans.The FilterChainProxy is
- * loaded via a standard {@link org.springframework.security.util.FilterToBeanProxy} declaration in web.xml.
+ * Delegates Filter requests to a list of Spring-managed beans.
+ * As of version 2.0, you shouldn't need to explicitly configure a FilterChainProxy 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 <security:http /> namespace configuration options.
+ *
+ *
The FilterChainProxy is loaded via a standard
+ * {@link org.springframework.security.util.FilterToBeanProxy} declaration in web.xml.
* FilterChainProxy will then pass {@link #init(FilterConfig)}, {@link #destroy()} and {@link
* #doFilter(ServletRequest, ServletResponse, FilterChain)} invocations through to each Filter defined
* against FilterChainProxy.
FilterChainProxy is configured using a standard {@link
- * org.springframework.security.intercept.web.FilterInvocationDefinitionSource}. Each possible URI pattern that
- * FilterChainProxy should service must be entered. The first matching URI pattern located by
- * FilterInvocationDefinitionSource for a given request will be used to define all of the
+ *
+ *
As of version 2.0, FilterChainProxy 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 FilterChainMap 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: + * + *
+ <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> + *+ * + * The names "filter1", "filter2", "filter3" should be the bean names of Filter 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. + * + * + *
+ * Each possible URI pattern that FilterChainProxy should service must be entered.
+ * The first matching URI pattern for a given request will be used to define all of the
* Filters that apply to that request. NB: This means you must put most specific URI patterns at the top
* of the list, and ensure all Filters that should apply for a given URI pattern are entered against the
* respective entry. The FilterChainProxy will not iterate the remainder of the URI patterns to locate
- * additional Filters. The FilterInvocationDefinitionSource 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.
Filters.
* FilterChainProxy respects normal handling of Filters 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 FilterChainProxy-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 FilterToBeanProxy will
* never call this class' {@link #init(FilterConfig)} and {@link #destroy()} methods, meaning each of the filters
- * defined against FilterInvocationDefinitionSource 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 lifecycle initialization parameter against the
* FilterToBeanProxy to specify servlet container lifecycle management.
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.
* * @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 { - FilterInvocation fi = new FilterInvocation(request, response, chain); - - ConfigAttributeDefinition cad = this.filterInvocationDefinitionSource.getAttributes(fi); - - if (cad == null) { - if (logger.isDebugEnabled()) { - logger.debug(fi.getRequestUrl() + " has no matching filters"); - } - - chain.doFilter(request, response); + throws IOException, ServletException { - return; - } + FilterInvocation fi = new FilterInvocation(request, response, chain); - Filter[] filters = obtainAllDefinedFilters(cad); + Filter[] filters = filterChainMap.getFilters(fi.getRequestUrl()); - if (filters.length == 0) { + if (filters == null || 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 uniqueFilter instances registered against the
- * FilterInvocationDefinitionSource.This is useful in ensuring a Filter is not
+ * Obtains all of the uniqueFilter instances registered in the
+ * FilterChainMap.
+ *
This is useful in ensuring a Filter is not
* initialized or destroyed twice.
Filter instances in the application context for which there has been an entry
- * against the FilterInvocationDefinitionSource (only one entry is included in the array for
+ * @deprecated
+ * @return all of the Filter instances in the application context which have an entry
+ * in the FilterChainMap (only one entry is included in the array for
* each Filter that actually exists in application context, even if a given
- * Filter is defined multiples times by the FilterInvocationDefinitionSource)
+ * Filter is defined multiples times by the FilterChainMap)
*/
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 filterChainMap.getAllDefinedFilters();
+ }
- return (Filter[]) list.toArray(new Filter[0]);
+ public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+ this.applicationContext = applicationContext;
}
/**
- * Obtains all of the Filter instances registered against the specified
- * ConfigAttributeDefinition.
- *
- * @param configAttributeDefinition for which we want to obtain associated Filters
- *
- * @return the Filters against the specified ConfigAttributeDefinition (never
- * null)
*
- * @throws IllegalArgumentException DOCUMENT ME!
+ * @deprecated Use namespace configuration or call setFilterChainMap instead.
*/
- 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 setFilterInvocationDefinitionSource(FilterInvocationDefinitionSource fids) {
+ this.fids = fids;
}
- public void setApplicationContext(ApplicationContext applicationContext)
- throws BeansException {
- this.applicationContext = applicationContext;
+ public void setFilterChainMap(FilterChainMap filterChainMap) {
+ this.filterChainMap = filterChainMap;
}
- public void setFilterInvocationDefinitionSource(FilterInvocationDefinitionSource filterInvocationDefinitionSource) {
- this.filterInvocationDefinitionSource = filterInvocationDefinitionSource;
+ public FilterChainMap getFilterChainMap() {
+ return filterChainMap;
}
//~ Inner Classes ==================================================================================================
@@ -242,7 +219,7 @@ public class FilterChainProxy implements Filter, InitializingBean, ApplicationCo
* FilterChain is used by FilterChainProxy to determine if the next Filter
* should be called or not.
*/
- 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
}
}
}
+
}
diff --git a/core/src/main/java/org/springframework/security/util/RegexUrlPathMatcher.java b/core/src/main/java/org/springframework/security/util/RegexUrlPathMatcher.java
new file mode 100644
index 0000000000..8776a95ec0
--- /dev/null
+++ b/core/src/main/java/org/springframework/security/util/RegexUrlPathMatcher.java
@@ -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 "/.*";
+ }
+}
diff --git a/core/src/main/java/org/springframework/security/util/UrlMatcher.java b/core/src/main/java/org/springframework/security/util/UrlMatcher.java
new file mode 100644
index 0000000000..1345353401
--- /dev/null
+++ b/core/src/main/java/org/springframework/security/util/UrlMatcher.java
@@ -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();
+}
diff --git a/core/src/test/java/org/springframework/security/util/FilterChainProxyTests.java b/core/src/test/java/org/springframework/security/util/FilterChainProxyTests.java
index 7d758b75dd..744874707d 100644
--- a/core/src/test/java/org/springframework/security/util/FilterChainProxyTests.java
+++ b/core/src/test/java/org/springframework/security/util/FilterChainProxyTests.java
@@ -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 class FilterChainProxyTests {
+ private ClassPathXmlApplicationContext appCtx;
- // ===========================================================
- public FilterChainProxyTests() {
- super();
- }
+ //~ Methods ========================================================================================================
- public FilterChainProxyTests(String arg0) {
- super(arg0);
+ @Before
+ public void loadContext() {
+ appCtx = new ClassPathXmlApplicationContext("org/springframework/security/util/filtertest-valid.xml");
}
- //~ Methods ========================================================================================================
-
- // ================================================================
- public static void main(String[] args) {
- junit.textui.TestRunner.run(FilterChainProxyTests.class);
+ @After
+ public void closeContext() {
+ if (appCtx != null) {
+ appCtx.close();
+ }
}
- public void testDetectsFilterInvocationDefinitionSourceThatDoesNotReturnAllConfigAttributes()
- throws Exception {
+ @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 ==================================================================================================
diff --git a/core/src/test/resources/org/springframework/security/util/filtertest-valid.xml b/core/src/test/resources/org/springframework/security/util/filtertest-valid.xml
index f000cf5ef0..082bb95f52 100644
--- a/core/src/test/resources/org/springframework/security/util/filtertest-valid.xml
+++ b/core/src/test/resources/org/springframework/security/util/filtertest-valid.xml
@@ -1,5 +1,5 @@
-
+
-
-