|
@@ -17,64 +17,100 @@ package org.springframework.security.web;
|
|
|
|
|
|
import org.apache.commons.logging.Log;
|
|
|
import org.apache.commons.logging.LogFactory;
|
|
|
+import org.springframework.security.web.firewall.DefaultHttpFirewall;
|
|
|
+import org.springframework.security.web.firewall.FirewalledRequest;
|
|
|
+import org.springframework.security.web.firewall.HttpFirewall;
|
|
|
import org.springframework.security.web.util.AnyRequestMatcher;
|
|
|
import org.springframework.security.web.util.RequestMatcher;
|
|
|
+import org.springframework.security.web.util.UrlUtils;
|
|
|
import org.springframework.util.Assert;
|
|
|
import org.springframework.web.filter.DelegatingFilterProxy;
|
|
|
import org.springframework.web.filter.GenericFilterBean;
|
|
|
|
|
|
-import javax.servlet.*;
|
|
|
+import javax.servlet.Filter;
|
|
|
+import javax.servlet.FilterChain;
|
|
|
+import javax.servlet.ServletException;
|
|
|
+import javax.servlet.ServletRequest;
|
|
|
+import javax.servlet.ServletRequestWrapper;
|
|
|
+import javax.servlet.ServletResponse;
|
|
|
import javax.servlet.http.HttpServletRequest;
|
|
|
+import javax.servlet.http.HttpServletResponse;
|
|
|
import java.io.IOException;
|
|
|
import java.util.*;
|
|
|
|
|
|
|
|
|
/**
|
|
|
- * 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
|
|
|
+ * Delegates {@code Filter} requests to a list of Spring-managed filter beans.
|
|
|
+ * As of version 2.0, you shouldn't need to explicitly configure a {@code 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 <tt><security:http /></tt> namespace configuration options.
|
|
|
+ * by the default {@code <security:http />} namespace configuration options.
|
|
|
* <p>
|
|
|
- * The <code>FilterChainProxy</code> is loaded via a standard Spring {@link DelegatingFilterProxy} declaration in
|
|
|
- * <code>web.xml</code>.
|
|
|
+ * The {@code FilterChainProxy} is linked into the servlet container filter chain by adding a standard
|
|
|
+ * Spring {@link DelegatingFilterProxy} declaration in the application {@code web.xml} file.
|
|
|
+ *
|
|
|
+ * <h2>Configuration</h2>
|
|
|
* <p>
|
|
|
- * As of version 3.1, <tt>FilterChainProxy</tt> is configured using an ordered Map of {@link RequestMatcher} instances
|
|
|
- * to <tt>List</tt>s of <tt>Filter</tt>s. The Map instance will normally be created while parsing the namespace
|
|
|
- * configuration, so doesn't have to be set explicitly. 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:
|
|
|
+ * As of version 3.1, {@code FilterChainProxy} is configured using an ordered Map of {@link RequestMatcher} instances
|
|
|
+ * to {@code List}s of {@code Filter}s. The Map instance will normally be created while parsing the namespace
|
|
|
+ * configuration, so doesn't have to be set explicitly. Instead the {@code <filter-chain-map>}
|
|
|
+ * element should be used within the bean declaration.
|
|
|
+ * This in turn should have a list of child {@code <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.
|
|
|
+ * The default pattern matching strategy is to use {@link org.springframework.security.web.util.AntPathRequestMatcher
|
|
|
+ * Ant-style paths}. An example configuration might look like this:
|
|
|
*
|
|
|
* <pre>
|
|
|
<bean id="myfilterChainProxy" class="org.springframework.security.util.FilterChainProxy">
|
|
|
<security:filter-chain-map request-matcher="ant">
|
|
|
- <security:filter-chain pattern="/do/not/filter" filters="none"/>
|
|
|
+ <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
|
|
|
+ * The names "filter1", "filter2", "filter3" should be the bean names of {@code 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 a request pattern from the security filter chain
|
|
|
* entirely. Please consult the security namespace schema file for a full list of available configuration options.
|
|
|
+ *
|
|
|
+ * <h2>Request Handling</h2>
|
|
|
* <p>
|
|
|
- * Each possible pattern that <code>FilterChainProxy</code> should service must be entered.
|
|
|
- * The first match for a given request will be used to define all of the <code>Filter</code>s that apply to that
|
|
|
- * request. This means you must put most specific matches at the top of the list, and ensure all <code>Filter</code>s
|
|
|
+ * Each possible pattern that the {@code FilterChainProxy} should service must be entered.
|
|
|
+ * The first match for a given request will be used to define all of the {@code Filter}s that apply to that
|
|
|
+ * request. This means you must put most specific matches at the top of the list, and ensure all {@code Filter}s
|
|
|
* that should apply for a given matcher are entered against the respective entry.
|
|
|
- * The <code>FilterChainProxy</code> will not iterate through the remainder of the map entries to locate additional
|
|
|
- * <code>Filter</code>s.
|
|
|
+ * The {@code FilterChainProxy} will not iterate through the remainder of the map entries to locate additional
|
|
|
+ * {@code Filter}s.
|
|
|
* <p>
|
|
|
- * <code>FilterChainProxy</code> respects normal handling of <code>Filter</code>s that elect not to call {@link
|
|
|
+ * {@code FilterChainProxy} respects normal handling of {@code Filter}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 original or <code>FilterChainProxy</code>-declared filter
|
|
|
+ * javax.servlet.FilterChain)}, in that the remainder of the original or {@code FilterChainProxy}-declared filter
|
|
|
* chain will not be called.
|
|
|
+ *
|
|
|
+ * <h3>Request Firewalling</h3>
|
|
|
+ *
|
|
|
+ * An {@link HttpFirewall} instance is used to validate incoming requests and create a wrapped request which provides
|
|
|
+ * consistent path values for matching against. See {@link DefaultHttpFirewall}, for more information on the type of
|
|
|
+ * attacks which the default implementation protects against. A custom implementation can be injected to provide
|
|
|
+ * stricter control over the request contents or if an application needs to support certain types of request which
|
|
|
+ * are rejected by default.
|
|
|
+ * <p>
|
|
|
+ * Note that this means that you must use the Spring Security filters in combination with a {@code FilterChainProxy}
|
|
|
+ * if you want this protection. Don't define them explicitly in your {@code web.xml} file.
|
|
|
* <p>
|
|
|
- * Note the <code>Filter</code> lifecycle mismatch between the servlet container and IoC
|
|
|
- * container. As described in the {@link DelegatingFilterProxy} JavaDocs, we recommend you allow the IoC
|
|
|
- * container to manage the lifecycle instead of the servlet container.
|
|
|
+ * {@code FilterChainProxy} will use the firewall instance to obtain both request and response objects which will be
|
|
|
+ * fed down the filter chain, so it is also possible to use this functionality to control the functionality of the
|
|
|
+ * response. When the request has passed through the security filter chain, the {@code reset} method will be called.
|
|
|
+ * With the default implementation this means that the original values of {@code servletPath} and {@code pathInfo} will
|
|
|
+ * be returned thereafter, instead of the modified ones used for security pattern matching.
|
|
|
+ *
|
|
|
+ *
|
|
|
+ * <h2>Filter Lifecycle</h2>
|
|
|
+ * <p>
|
|
|
+ * Note the {@code Filter} lifecycle mismatch between the servlet container and IoC
|
|
|
+ * container. As described in the {@link DelegatingFilterProxy} Javadocs, we recommend you allow the IoC
|
|
|
+ * container to manage the lifecycle instead of the servlet container. {@code FilterChainProxy} does not invoke the
|
|
|
+ * standard filter lifecycle methods on any filter beans that you add to the application context.
|
|
|
*
|
|
|
* @author Carlos Sanchez
|
|
|
* @author Ben Alex
|
|
@@ -92,6 +128,8 @@ public class FilterChainProxy extends GenericFilterBean {
|
|
|
|
|
|
private FilterChainValidator filterChainValidator = new NullFilterChainValidator();
|
|
|
|
|
|
+ private HttpFirewall firewall = new DefaultHttpFirewall();
|
|
|
+
|
|
|
//~ Methods ========================================================================================================
|
|
|
|
|
|
@Override
|
|
@@ -103,25 +141,27 @@ public class FilterChainProxy extends GenericFilterBean {
|
|
|
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
|
|
|
throws IOException, ServletException {
|
|
|
|
|
|
- FilterInvocation fi = new FilterInvocation(request, response, chain);
|
|
|
- List<Filter> filters = getFilters(fi.getRequest());
|
|
|
+ FirewalledRequest fwRequest = firewall.getFirewalledRequest((HttpServletRequest) request);
|
|
|
+ HttpServletResponse fwResponse = firewall.getFirewalledResponse((HttpServletResponse) response);
|
|
|
+ String url = UrlUtils.buildRequestUrl(fwRequest);
|
|
|
+
|
|
|
+ List<Filter> filters = getFilters(fwRequest);
|
|
|
|
|
|
if (filters == null || filters.size() == 0) {
|
|
|
if (logger.isDebugEnabled()) {
|
|
|
- logger.debug(fi.getRequestUrl() +
|
|
|
+ logger.debug(url +
|
|
|
(filters == null ? " has no matching filters" : " has an empty filter list"));
|
|
|
}
|
|
|
|
|
|
- chain.doFilter(request, response);
|
|
|
+ chain.doFilter(fwRequest, fwResponse);
|
|
|
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- VirtualFilterChain virtualFilterChain = new VirtualFilterChain(fi, filters);
|
|
|
- virtualFilterChain.doFilter(fi.getRequest(), fi.getResponse());
|
|
|
+ VirtualFilterChain vfc = new VirtualFilterChain(url, chain, filters);
|
|
|
+ vfc.doFilter(fwRequest, fwResponse);
|
|
|
}
|
|
|
|
|
|
-
|
|
|
/**
|
|
|
* Returns the first filter chain matching the supplied URL.
|
|
|
*
|
|
@@ -147,20 +187,20 @@ public class FilterChainProxy extends GenericFilterBean {
|
|
|
* @return matching filter list
|
|
|
*/
|
|
|
public List<Filter> getFilters(String url) {
|
|
|
- return getFilters(new FilterInvocation(url, null).getRequest());
|
|
|
+ return getFilters(firewall.getFirewalledRequest((new FilterInvocation(url, null).getRequest())));
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Sets the mapping of URL patterns to filter chains.
|
|
|
*
|
|
|
- * The map keys should be the paths and the values should be arrays of <tt>Filter</tt> objects.
|
|
|
+ * The map keys should be the paths and the values should be arrays of {@code Filter} objects.
|
|
|
* It's VERY important that the type of map used preserves ordering - the order in which the iterator
|
|
|
* returns the entries must be the same as the order they were added to the map, otherwise you have no way
|
|
|
* of guaranteeing that the most specific patterns are returned before the more general ones. So make sure
|
|
|
- * the Map used is an instance of <tt>LinkedHashMap</tt> or an equivalent, rather than a plain <tt>HashMap</tt>, for
|
|
|
+ * the Map used is an instance of {@code LinkedHashMap} or an equivalent, rather than a plain {@code HashMap}, for
|
|
|
* example.
|
|
|
*
|
|
|
- * @param filterChainMap the map of path Strings to <tt>List<Filter></tt>s.
|
|
|
+ * @param filterChainMap the map of path Strings to {@code List<Filter>}s.
|
|
|
*/
|
|
|
@SuppressWarnings("unchecked")
|
|
|
public void setFilterChainMap(Map filterChainMap) {
|
|
@@ -198,7 +238,7 @@ public class FilterChainProxy extends GenericFilterBean {
|
|
|
|
|
|
/**
|
|
|
* Returns a copy of the underlying filter chain map. Modifications to the map contents
|
|
|
- * will not affect the FilterChainProxy state - to change the map call <tt>setFilterChainMap</tt>.
|
|
|
+ * will not affect the FilterChainProxy state - to change the map call {@code setFilterChainMap}.
|
|
|
*
|
|
|
* @return the map of path pattern Strings to filter chain lists (with ordering guaranteed).
|
|
|
*/
|
|
@@ -216,6 +256,16 @@ public class FilterChainProxy extends GenericFilterBean {
|
|
|
this.filterChainValidator = filterChainValidator;
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Sets the "firewall" implementation which will be used to validate and wrap (or potentially reject) the
|
|
|
+ * incoming requests. The default implementation should be satisfactory for most requirements.
|
|
|
+ *
|
|
|
+ * @param firewall
|
|
|
+ */
|
|
|
+ public void setFirewall(HttpFirewall firewall) {
|
|
|
+ this.firewall = firewall;
|
|
|
+ }
|
|
|
+
|
|
|
public String toString() {
|
|
|
StringBuilder sb = new StringBuilder();
|
|
|
sb.append("FilterChainProxy[");
|
|
@@ -230,18 +280,18 @@ public class FilterChainProxy extends GenericFilterBean {
|
|
|
|
|
|
/**
|
|
|
* Internal {@code FilterChain} implementation that is used to pass a request through the additional
|
|
|
- * internal list of filters which match the request. Records the position in the additional filter chain and, when
|
|
|
- * completed, passes the request back to the original {@code FilterChain} supplied by the servlet container.
|
|
|
+ * internal list of filters which match the request.
|
|
|
*/
|
|
|
private static class VirtualFilterChain implements FilterChain {
|
|
|
- private final FilterInvocation fi;
|
|
|
+ private final FilterChain originalChain;
|
|
|
private final List<Filter> additionalFilters;
|
|
|
+ private final String url;
|
|
|
private final int size;
|
|
|
private int currentPosition = 0;
|
|
|
|
|
|
-
|
|
|
- private VirtualFilterChain(FilterInvocation filterInvocation, List<Filter> additionalFilters) {
|
|
|
- this.fi = filterInvocation;
|
|
|
+ private VirtualFilterChain(String url, FilterChain chain, List<Filter> additionalFilters) {
|
|
|
+ this.originalChain = chain;
|
|
|
+ this.url = url;
|
|
|
this.additionalFilters = additionalFilters;
|
|
|
this.size = additionalFilters.size();
|
|
|
}
|
|
@@ -249,23 +299,35 @@ public class FilterChainProxy extends GenericFilterBean {
|
|
|
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
|
|
|
if (currentPosition == size) {
|
|
|
if (logger.isDebugEnabled()) {
|
|
|
- logger.debug(fi.getRequestUrl()
|
|
|
- + " reached end of additional filter chain; proceeding with original chain");
|
|
|
+ logger.debug(url + " reached end of additional filter chain; proceeding with original chain");
|
|
|
}
|
|
|
|
|
|
- fi.getChain().doFilter(request, response);
|
|
|
+ // Deactivate path stripping as we exit the security filter chain
|
|
|
+ resetWrapper(request);
|
|
|
+
|
|
|
+ originalChain.doFilter(request, response);
|
|
|
} else {
|
|
|
currentPosition++;
|
|
|
|
|
|
Filter nextFilter = additionalFilters.get(currentPosition - 1);
|
|
|
|
|
|
if (logger.isDebugEnabled()) {
|
|
|
- logger.debug(fi.getRequestUrl() + " at position " + currentPosition + " of "
|
|
|
+ logger.debug(url + " at position " + currentPosition + " of "
|
|
|
+ size + " in additional filter chain; firing Filter: '"
|
|
|
- + nextFilter + "'");
|
|
|
+ + nextFilter.getClass().getSimpleName() + "'");
|
|
|
}
|
|
|
|
|
|
- nextFilter.doFilter(request, response, this);
|
|
|
+ nextFilter.doFilter(request, response, this);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void resetWrapper(ServletRequest request) {
|
|
|
+ while (request instanceof ServletRequestWrapper) {
|
|
|
+ if (request instanceof FirewalledRequest) {
|
|
|
+ ((FirewalledRequest)request).reset();
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ request = ((ServletRequestWrapper)request).getRequest();
|
|
|
}
|
|
|
}
|
|
|
}
|