All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springframework.security.web.FilterChainProxy Maven / Gradle / Ivy

There is a newer version: 6.2.4
Show newest version
/*
 * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.security.web;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.firewall.FirewalledRequest;
import org.springframework.security.web.firewall.HttpFirewall;
import org.springframework.security.web.firewall.StrictHttpFirewall;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.security.web.util.UrlUtils;
import org.springframework.web.filter.DelegatingFilterProxy;
import org.springframework.web.filter.GenericFilterBean;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;

/**
 * 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
 * {@code } namespace configuration options.
 * 

* 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. * *

Configuration

*

* As of version 3.1, {@code FilterChainProxy} is configured using a list of * {@link SecurityFilterChain} instances, each of which contains a {@link RequestMatcher} * and a list of filters which should be applied to matching requests. Most applications * will only contain a single filter chain, and if you are using the namespace, you don't * have to set the chains explicitly. If you require finer-grained control, you can make * use of the {@code } namespace element. This defines 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">
 *      <constructor-arg>
 *          <util:list>
 *              <security:filter-chain pattern="/do/not/filter*" filters="none"/>
 *              <security:filter-chain pattern="/**" filters="filter1,filter2,filter3"/>
 *          </util:list>
 *      </constructor-arg>
 *  </bean>
 * 
* * 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. * *

Request Handling

*

* 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} will not iterate * through the remainder of the map entries to locate additional {@code Filter}s. *

* {@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}-declared filter * chain will not be called. * *

Request Firewalling

* * 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 StrictHttpFirewall}, 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. *

* 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. *

* {@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. *

* Since this additional wrapping functionality is performed by the * {@code FilterChainProxy}, we don't recommend that you use multiple instances in the * same filter chain. It shouldn't be considered purely as a utility for wrapping filter * beans in a single {@code Filter} instance. * *

Filter Lifecycle

*

* 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 * @author Luke Taylor * @author Rob Winch */ public class FilterChainProxy extends GenericFilterBean { // ~ Static fields/initializers // ===================================================================================== private static final Log logger = LogFactory.getLog(FilterChainProxy.class); // ~ Instance fields // ================================================================================================ private final static String FILTER_APPLIED = FilterChainProxy.class.getName().concat( ".APPLIED"); private List filterChains; private FilterChainValidator filterChainValidator = new NullFilterChainValidator(); private HttpFirewall firewall = new StrictHttpFirewall(); // ~ Methods // ======================================================================================================== public FilterChainProxy() { } public FilterChainProxy(SecurityFilterChain chain) { this(Arrays.asList(chain)); } public FilterChainProxy(List filterChains) { this.filterChains = filterChains; } @Override public void afterPropertiesSet() { filterChainValidator.validate(this); } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { boolean clearContext = request.getAttribute(FILTER_APPLIED) == null; if (clearContext) { try { request.setAttribute(FILTER_APPLIED, Boolean.TRUE); doFilterInternal(request, response, chain); } finally { SecurityContextHolder.clearContext(); request.removeAttribute(FILTER_APPLIED); } } else { doFilterInternal(request, response, chain); } } private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { FirewalledRequest fwRequest = firewall .getFirewalledRequest((HttpServletRequest) request); HttpServletResponse fwResponse = firewall .getFirewalledResponse((HttpServletResponse) response); List filters = getFilters(fwRequest); if (filters == null || filters.size() == 0) { if (logger.isDebugEnabled()) { logger.debug(UrlUtils.buildRequestUrl(fwRequest) + (filters == null ? " has no matching filters" : " has an empty filter list")); } fwRequest.reset(); chain.doFilter(fwRequest, fwResponse); return; } VirtualFilterChain vfc = new VirtualFilterChain(fwRequest, chain, filters); vfc.doFilter(fwRequest, fwResponse); } /** * Returns the first filter chain matching the supplied URL. * * @param request the request to match * @return an ordered array of Filters defining the filter chain */ private List getFilters(HttpServletRequest request) { for (SecurityFilterChain chain : filterChains) { if (chain.matches(request)) { return chain.getFilters(); } } return null; } /** * Convenience method, mainly for testing. * * @param url the URL * @return matching filter list */ public List getFilters(String url) { return getFilters(firewall.getFirewalledRequest((new FilterInvocation(url, "GET") .getRequest()))); } /** * @return the list of {@code SecurityFilterChain}s which will be matched against and * applied to incoming requests. */ public List getFilterChains() { return Collections.unmodifiableList(filterChains); } /** * Used (internally) to specify a validation strategy for the filters in each * configured chain. * * @param filterChainValidator the validator instance which will be invoked on during * initialization to check the {@code FilterChainProxy} instance. */ public void setFilterChainValidator(FilterChainValidator filterChainValidator) { 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; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("FilterChainProxy["); sb.append("Filter Chains: "); sb.append(filterChains); sb.append("]"); return sb.toString(); } // ~ Inner Classes // ================================================================================================== /** * 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 final FilterChain originalChain; private final List additionalFilters; private final FirewalledRequest firewalledRequest; private final int size; private int currentPosition = 0; private VirtualFilterChain(FirewalledRequest firewalledRequest, FilterChain chain, List additionalFilters) { this.originalChain = chain; this.additionalFilters = additionalFilters; this.size = additionalFilters.size(); this.firewalledRequest = firewalledRequest; } @Override public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException { if (currentPosition == size) { if (logger.isDebugEnabled()) { logger.debug(UrlUtils.buildRequestUrl(firewalledRequest) + " reached end of additional filter chain; proceeding with original chain"); } // Deactivate path stripping as we exit the security filter chain this.firewalledRequest.reset(); originalChain.doFilter(request, response); } else { currentPosition++; Filter nextFilter = additionalFilters.get(currentPosition - 1); if (logger.isDebugEnabled()) { logger.debug(UrlUtils.buildRequestUrl(firewalledRequest) + " at position " + currentPosition + " of " + size + " in additional filter chain; firing Filter: '" + nextFilter.getClass().getSimpleName() + "'"); } nextFilter.doFilter(request, response, this); } } } public interface FilterChainValidator { void validate(FilterChainProxy filterChainProxy); } private static class NullFilterChainValidator implements FilterChainValidator { @Override public void validate(FilterChainProxy filterChainProxy) { } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy