|
@@ -15,37 +15,37 @@
|
|
|
|
|
|
package org.springframework.security.web;
|
|
|
|
|
|
-import java.io.IOException;
|
|
|
-import java.util.Collection;
|
|
|
-import java.util.Iterator;
|
|
|
-import java.util.LinkedHashMap;
|
|
|
-import java.util.LinkedHashSet;
|
|
|
-import java.util.List;
|
|
|
-import java.util.Map;
|
|
|
-import java.util.Set;
|
|
|
-
|
|
|
-import javax.servlet.Filter;
|
|
|
-import javax.servlet.FilterChain;
|
|
|
-import javax.servlet.FilterConfig;
|
|
|
-import javax.servlet.ServletException;
|
|
|
-import javax.servlet.ServletRequest;
|
|
|
-import javax.servlet.ServletResponse;
|
|
|
-
|
|
|
import org.apache.commons.logging.Log;
|
|
|
import org.apache.commons.logging.LogFactory;
|
|
|
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
|
|
|
+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.AntUrlPathMatcher;
|
|
|
import org.springframework.security.web.util.UrlMatcher;
|
|
|
+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.Filter;
|
|
|
+import javax.servlet.FilterChain;
|
|
|
+import javax.servlet.FilterConfig;
|
|
|
+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 <tt><security:http /></tt> namespace configuration options.
|
|
|
*
|
|
|
* <p>The <code>FilterChainProxy</code> is loaded via a standard Spring {@link DelegatingFilterProxy} declaration in
|
|
|
* <code>web.xml</code>. <code>FilterChainProxy</code> will then pass {@link #init(FilterConfig)}, {@link #destroy()}
|
|
@@ -71,7 +71,7 @@ import org.springframework.web.filter.GenericFilterBean;
|
|
|
</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
|
|
|
* Please consult the security namespace schema file for a full list of available configuration options.
|
|
@@ -86,17 +86,32 @@ import org.springframework.web.filter.GenericFilterBean;
|
|
|
*
|
|
|
* <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 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.
|
|
|
*
|
|
|
- * <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. By default the <code>DelegatingFilterProxy</code>
|
|
|
- * will never call this class' {@link #init(FilterConfig)} and {@link #destroy()} methods, which in turns means that
|
|
|
- * the corresponding methods on the filter beans managed by this class will never be called. If you do need your filters to be
|
|
|
- * initialized and destroyed, please set the <tt>targetFilterLifecycle</tt> initialization parameter against the
|
|
|
- * <code>DelegatingFilterProxy</code> to specify that servlet container lifecycle management should be used. You don't
|
|
|
- * need to worry about this in most cases.
|
|
|
+ * <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>
|
|
|
+ * {@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
|
|
@@ -118,6 +133,7 @@ public class FilterChainProxy extends GenericFilterBean {
|
|
|
private Map<Object, List<Filter>> filterChainMap;
|
|
|
private UrlMatcher matcher = new AntUrlPathMatcher();
|
|
|
private boolean stripQueryStringFromUrls = true;
|
|
|
+ private HttpFirewall firewall = new DefaultHttpFirewall();
|
|
|
private FilterChainValidator filterChainValidator = new NullFilterChainValidator();
|
|
|
|
|
|
//~ Methods ========================================================================================================
|
|
@@ -131,22 +147,24 @@ 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.getRequestUrl());
|
|
|
+ FirewalledRequest fwRequest = firewall.getFirewalledRequest((HttpServletRequest) request);
|
|
|
+ HttpServletResponse fwResponse = firewall.getFirewalledResponse((HttpServletResponse) response);
|
|
|
+ String url = UrlUtils.buildRequestUrl(fwRequest);
|
|
|
+
|
|
|
+ List<Filter> filters = getFilters(url);
|
|
|
|
|
|
if (filters == null || filters.size() == 0) {
|
|
|
if (logger.isDebugEnabled()) {
|
|
|
- logger.debug(fi.getRequestUrl() +
|
|
|
- filters == null ? " has no matching filters" : " has an empty filter list");
|
|
|
+ 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);
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -213,14 +231,14 @@ public class FilterChainProxy extends GenericFilterBean {
|
|
|
/**
|
|
|
* 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) {
|
|
@@ -270,7 +288,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).
|
|
|
*/
|
|
@@ -317,42 +335,53 @@ public class FilterChainProxy extends GenericFilterBean {
|
|
|
//~ Inner Classes ==================================================================================================
|
|
|
|
|
|
/**
|
|
|
- * A <code>FilterChain</code> that records whether or not {@link
|
|
|
- * FilterChain#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse)} is called.
|
|
|
- * <p>
|
|
|
- * This <code>FilterChain</code> is used by <code>FilterChainProxy</code> to determine if the next
|
|
|
- * <code>Filter</code> should be called or not.</p>
|
|
|
+ * Internal {@code FilterChain} implementation that is used to pass a request through the additional
|
|
|
+ * internal list of filters which match the request.
|
|
|
*/
|
|
|
private static class VirtualFilterChain implements FilterChain {
|
|
|
- private FilterInvocation fi;
|
|
|
- private List<Filter> additionalFilters;
|
|
|
+ private final FilterChain originalChain;
|
|
|
+ private final List<Filter> additionalFilters;
|
|
|
+ private final String url;
|
|
|
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;
|
|
|
}
|
|
|
|
|
|
- public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
|
|
|
+ public void doFilter(final ServletRequest request, final ServletResponse response) throws IOException, ServletException {
|
|
|
if (currentPosition == additionalFilters.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 "
|
|
|
+ additionalFilters.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();
|
|
|
}
|
|
|
}
|
|
|
}
|