/* * Copyright 2002-2013 the original author or authors. * * 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.config.annotation.web.builders; import java.util.ArrayList; import java.util.List; import javax.servlet.Filter; import javax.servlet.http.HttpServletRequest; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.http.HttpMethod; import org.springframework.security.access.expression.SecurityExpressionHandler; import org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder; import org.springframework.security.config.annotation.ObjectPostProcessor; import org.springframework.security.config.annotation.SecurityBuilder; import org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry; import org.springframework.security.config.annotation.web.WebSecurityConfigurer; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.web.DefaultSecurityFilterChain; import org.springframework.security.web.FilterChainProxy; import org.springframework.security.web.FilterInvocation; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.access.DefaultWebInvocationPrivilegeEvaluator; import org.springframework.security.web.access.WebInvocationPrivilegeEvaluator; import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler; import org.springframework.security.web.access.intercept.FilterSecurityInterceptor; import org.springframework.security.web.debug.DebugFilter; import org.springframework.security.web.firewall.DefaultHttpFirewall; import org.springframework.security.web.firewall.HttpFirewall; import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.util.Assert; import org.springframework.web.filter.DelegatingFilterProxy; /** * <p> * The {@link WebSecurity} is created by {@link WebSecurityConfiguration} to create the * {@link FilterChainProxy} known as the Spring Security Filter Chain * (springSecurityFilterChain). The springSecurityFilterChain is the {@link Filter} that * the {@link DelegatingFilterProxy} delegates to. * </p> * * <p> * Customizations to the {@link WebSecurity} can be made by creating a * {@link WebSecurityConfigurer} or more likely by overriding * {@link WebSecurityConfigurerAdapter}. * </p> * * @see EnableWebSecurity * @see WebSecurityConfiguration * * @author Rob Winch * @since 3.2 */ public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter, WebSecurity> implements SecurityBuilder<Filter>, ApplicationContextAware { private final Log logger = LogFactory.getLog(getClass()); private final List<RequestMatcher> ignoredRequests = new ArrayList<RequestMatcher>(); private final List<SecurityBuilder<? extends SecurityFilterChain>> securityFilterChainBuilders = new ArrayList<SecurityBuilder<? extends SecurityFilterChain>>(); private IgnoredRequestConfigurer ignoredRequestRegistry; private FilterSecurityInterceptor filterSecurityInterceptor; private HttpFirewall httpFirewall; private boolean debugEnabled; private WebInvocationPrivilegeEvaluator privilegeEvaluator; private DefaultWebSecurityExpressionHandler defaultWebSecurityExpressionHandler = new DefaultWebSecurityExpressionHandler(); private SecurityExpressionHandler<FilterInvocation> expressionHandler = defaultWebSecurityExpressionHandler; private Runnable postBuildAction = new Runnable() { public void run() { } }; /** * Creates a new instance * @param objectPostProcessor the {@link ObjectPostProcessor} to use * @see WebSecurityConfiguration */ public WebSecurity(ObjectPostProcessor<Object> objectPostProcessor) { super(objectPostProcessor); } /** * <p> * Allows adding {@link RequestMatcher} instances that Spring Security * should ignore. Web Security provided by Spring Security (including the * {@link SecurityContext}) will not be available on {@link HttpServletRequest} that * match. Typically the requests that are registered should be that of only static * resources. For requests that are dynamic, consider mapping the request to allow all * users instead. * </p> * * Example Usage: * * <pre> * webSecurityBuilder.ignoring() * // ignore all URLs that start with /resources/ or /static/ * .antMatchers("/resources/**", "/static/**"); * </pre> * * Alternatively this will accomplish the same result: * * <pre> * webSecurityBuilder.ignoring() * // ignore all URLs that start with /resources/ or /static/ * .antMatchers("/resources/**").antMatchers("/static/**"); * </pre> * * Multiple invocations of ignoring() are also additive, so the following is also * equivalent to the previous two examples: * * <pre> * webSecurityBuilder.ignoring() * // ignore all URLs that start with /resources/ * .antMatchers("/resources/**"); * webSecurityBuilder.ignoring() * // ignore all URLs that start with /static/ * .antMatchers("/static/**"); * // now both URLs that start with /resources/ and /static/ will be ignored * </pre> * * @return the {@link IgnoredRequestConfigurer} to use for registering request that * should be ignored */ public IgnoredRequestConfigurer ignoring() { return ignoredRequestRegistry; } /** * Allows customizing the {@link HttpFirewall}. The default is * {@link DefaultHttpFirewall}. * * @param httpFirewall the custom {@link HttpFirewall} * @return the {@link WebSecurity} for further customizations */ public WebSecurity httpFirewall(HttpFirewall httpFirewall) { this.httpFirewall = httpFirewall; return this; } /** * Controls debugging support for Spring Security. * * @param debugEnabled if true, enables debug support with Spring Security. Default is * false. * * @return the {@link WebSecurity} for further customization. * @see EnableWebSecurity#debug() */ public WebSecurity debug(boolean debugEnabled) { this.debugEnabled = debugEnabled; return this; } /** * <p> * Adds builders to create {@link SecurityFilterChain} instances. * </p> * * <p> * Typically this method is invoked automatically within the framework from * {@link WebSecurityConfigurerAdapter#init(WebSecurity)} * </p> * * @param securityFilterChainBuilder the builder to use to create the * {@link SecurityFilterChain} instances * @return the {@link WebSecurity} for further customizations */ public WebSecurity addSecurityFilterChainBuilder( SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder) { this.securityFilterChainBuilders.add(securityFilterChainBuilder); return this; } /** * Set the {@link WebInvocationPrivilegeEvaluator} to be used. If this is null, then a * {@link DefaultWebInvocationPrivilegeEvaluator} will be created when * {@link #securityInterceptor(FilterSecurityInterceptor)} is non null. * * @param privilegeEvaluator the {@link WebInvocationPrivilegeEvaluator} to use * @return the {@link WebSecurity} for further customizations */ public WebSecurity privilegeEvaluator( WebInvocationPrivilegeEvaluator privilegeEvaluator) { this.privilegeEvaluator = privilegeEvaluator; return this; } /** * Set the {@link SecurityExpressionHandler} to be used. If this is null, then a * {@link DefaultWebSecurityExpressionHandler} will be used. * * @param expressionHandler the {@link SecurityExpressionHandler} to use * @return the {@link WebSecurity} for further customizations */ public WebSecurity expressionHandler( SecurityExpressionHandler<FilterInvocation> expressionHandler) { Assert.notNull(expressionHandler, "expressionHandler cannot be null"); this.expressionHandler = expressionHandler; return this; } /** * Gets the {@link SecurityExpressionHandler} to be used. * @return */ public SecurityExpressionHandler<FilterInvocation> getExpressionHandler() { return expressionHandler; } /** * Gets the {@link WebInvocationPrivilegeEvaluator} to be used. * @return */ public WebInvocationPrivilegeEvaluator getPrivilegeEvaluator() { if (privilegeEvaluator != null) { return privilegeEvaluator; } return filterSecurityInterceptor == null ? null : new DefaultWebInvocationPrivilegeEvaluator(filterSecurityInterceptor); } /** * Sets the {@link FilterSecurityInterceptor}. This is typically invoked by * {@link WebSecurityConfigurerAdapter}. * @param securityInterceptor the {@link FilterSecurityInterceptor} to use * @return the {@link WebSecurity} for further customizations */ public WebSecurity securityInterceptor(FilterSecurityInterceptor securityInterceptor) { this.filterSecurityInterceptor = securityInterceptor; return this; } /** * Executes the Runnable immediately after the build takes place * * @param postBuildAction * @return the {@link WebSecurity} for further customizations */ public WebSecurity postBuildAction(Runnable postBuildAction) { this.postBuildAction = postBuildAction; return this; } @Override protected Filter performBuild() throws Exception { Assert.state( !securityFilterChainBuilders.isEmpty(), "At least one SecurityBuilder<? extends SecurityFilterChain> needs to be specified. Typically this done by adding a @Configuration that extends WebSecurityConfigurerAdapter. More advanced users can invoke " + WebSecurity.class.getSimpleName() + ".addSecurityFilterChainBuilder directly"); int chainSize = ignoredRequests.size() + securityFilterChainBuilders.size(); List<SecurityFilterChain> securityFilterChains = new ArrayList<SecurityFilterChain>( chainSize); for (RequestMatcher ignoredRequest : ignoredRequests) { securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest)); } for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) { securityFilterChains.add(securityFilterChainBuilder.build()); } FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains); if (httpFirewall != null) { filterChainProxy.setFirewall(httpFirewall); } filterChainProxy.afterPropertiesSet(); Filter result = filterChainProxy; if (debugEnabled) { logger.warn("\n\n" + "********************************************************************\n" + "********** Security debugging is enabled. *************\n" + "********** This may include sensitive information. *************\n" + "********** Do not use in a production system! *************\n" + "********************************************************************\n\n"); result = new DebugFilter(filterChainProxy); } postBuildAction.run(); return result; } /** * An {@link IgnoredRequestConfigurer} that allows optionally configuring the * {@link MvcRequestMatcher#setMethod(HttpMethod)} * * @author Rob Winch */ public final class MvcMatchersIgnoredRequestConfigurer extends IgnoredRequestConfigurer { private final List<MvcRequestMatcher> mvcMatchers; private MvcMatchersIgnoredRequestConfigurer(ApplicationContext context, List<MvcRequestMatcher> mvcMatchers) { super(context); this.mvcMatchers = mvcMatchers; } public IgnoredRequestConfigurer servletPath(String servletPath) { for (MvcRequestMatcher matcher : this.mvcMatchers) { matcher.setServletPath(servletPath); } return this; } } /** * Allows registering {@link RequestMatcher} instances that should be ignored by * Spring Security. * * @author Rob Winch * @since 3.2 */ public class IgnoredRequestConfigurer extends AbstractRequestMatcherRegistry<IgnoredRequestConfigurer> { private IgnoredRequestConfigurer(ApplicationContext context) { setApplicationContext(context); } @Override public MvcMatchersIgnoredRequestConfigurer mvcMatchers(HttpMethod method, String... mvcPatterns) { List<MvcRequestMatcher> mvcMatchers = createMvcMatchers(method, mvcPatterns); WebSecurity.this.ignoredRequests.addAll(mvcMatchers); return new MvcMatchersIgnoredRequestConfigurer(getApplicationContext(), mvcMatchers); } @Override public MvcMatchersIgnoredRequestConfigurer mvcMatchers(String... mvcPatterns) { return mvcMatchers(null, mvcPatterns); } @Override protected IgnoredRequestConfigurer chainRequestMatchers( List<RequestMatcher> requestMatchers) { WebSecurity.this.ignoredRequests.addAll(requestMatchers); return this; } /** * Returns the {@link WebSecurity} to be returned for chaining. */ public WebSecurity and() { return WebSecurity.this; } } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.defaultWebSecurityExpressionHandler .setApplicationContext(applicationContext); this.ignoredRequestRegistry = new IgnoredRequestConfigurer(applicationContext); } }