SEC-2422: Session timeout detection with CSRF protection

This commit is contained in:
Rob Winch 2013-12-11 17:38:17 -06:00
parent 00d668dc5c
commit 7f714ebb23
13 changed files with 523 additions and 30 deletions

View File

@ -15,14 +15,22 @@
*/
package org.springframework.security.config.annotation.web.configurers;
import java.util.LinkedHashMap;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.access.AccessDeniedHandlerImpl;
import org.springframework.security.web.access.DelegatingAccessDeniedHandler;
import org.springframework.security.web.csrf.CsrfAuthenticationStrategy;
import org.springframework.security.web.csrf.CsrfFilter;
import org.springframework.security.web.csrf.CsrfLogoutHandler;
import org.springframework.security.web.csrf.CsrfTokenRepository;
import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository;
import org.springframework.security.web.csrf.MissingCsrfTokenException;
import org.springframework.security.web.session.InvalidSessionAccessDeniedHandler;
import org.springframework.security.web.session.InvalidSessionStrategy;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
@ -50,6 +58,7 @@ import org.springframework.util.Assert;
* <li>
* {@link ExceptionHandlingConfigurer#accessDeniedHandler(AccessDeniedHandler)}
* is used to determine how to handle CSRF attempts</li>
* <li>{@link InvalidSessionStrategy}</li>
* </ul>
*
* @author Rob Winch
@ -100,12 +109,9 @@ public final class CsrfConfigurer<H extends HttpSecurityBuilder<H>> extends Abst
if(requireCsrfProtectionMatcher != null) {
filter.setRequireCsrfProtectionMatcher(requireCsrfProtectionMatcher);
}
ExceptionHandlingConfigurer<H> exceptionConfig = http.getConfigurer(ExceptionHandlingConfigurer.class);
if(exceptionConfig != null) {
AccessDeniedHandler accessDeniedHandler = exceptionConfig.getAccessDeniedHandler();
if(accessDeniedHandler != null) {
filter.setAccessDeniedHandler(accessDeniedHandler);
}
AccessDeniedHandler accessDeniedHandler = createAccessDeniedHandler(http);
if(accessDeniedHandler != null) {
filter.setAccessDeniedHandler(accessDeniedHandler);
}
LogoutConfigurer<H> logoutConfigurer = http.getConfigurer(LogoutConfigurer.class);
if(logoutConfigurer != null) {
@ -118,4 +124,70 @@ public final class CsrfConfigurer<H extends HttpSecurityBuilder<H>> extends Abst
filter = postProcess(filter);
http.addFilter(filter);
}
/**
* Gets the default {@link AccessDeniedHandler} from the
* {@link ExceptionHandlingConfigurer#getAccessDeniedHandler()} or create a
* {@link AccessDeniedHandlerImpl} if not available.
*
* @param http the {@link HttpSecurityBuilder}
* @return the {@link AccessDeniedHandler}
*/
@SuppressWarnings("unchecked")
private AccessDeniedHandler getDefaultAccessDeniedHandler(H http) {
ExceptionHandlingConfigurer<H> exceptionConfig = http.getConfigurer(ExceptionHandlingConfigurer.class);
AccessDeniedHandler handler = null;
if(exceptionConfig != null) {
handler = exceptionConfig.getAccessDeniedHandler();
}
if(handler == null) {
handler = new AccessDeniedHandlerImpl();
}
return handler;
}
/**
* Gets the default {@link InvalidSessionStrategy} from the
* {@link SessionManagementConfigurer#getInvalidSessionStrategy()} or null
* if not available.
*
* @param http
* the {@link HttpSecurityBuilder}
* @return the {@link InvalidSessionStrategy}
*/
@SuppressWarnings("unchecked")
private InvalidSessionStrategy getInvalidSessionStrategy(H http) {
SessionManagementConfigurer<H> sessionManagement = http.getConfigurer(SessionManagementConfigurer.class);
if(sessionManagement == null) {
return null;
}
return sessionManagement.getInvalidSessionStrategy();
}
/**
* Creates the {@link AccessDeniedHandler} from the result of
* {@link #getDefaultAccessDeniedHandler(HttpSecurityBuilder)} and
* {@link #getInvalidSessionStrategy(HttpSecurityBuilder)}. If
* {@link #getInvalidSessionStrategy(HttpSecurityBuilder)} is non-null, then
* a {@link DelegatingAccessDeniedHandler} is used in combination with
* {@link InvalidSessionAccessDeniedHandler} and the
* {@link #getDefaultAccessDeniedHandler(HttpSecurityBuilder)}. Otherwise,
* only {@link #getDefaultAccessDeniedHandler(HttpSecurityBuilder)} is used.
*
* @param http the {@link HttpSecurityBuilder}
* @return the {@link AccessDeniedHandler}
*/
private AccessDeniedHandler createAccessDeniedHandler(H http) {
InvalidSessionStrategy invalidSessionStrategy = getInvalidSessionStrategy(http);
AccessDeniedHandler defaultAccessDeniedHandler = getDefaultAccessDeniedHandler(http);
if(invalidSessionStrategy == null) {
return defaultAccessDeniedHandler;
}
InvalidSessionAccessDeniedHandler invalidSessionDeniedHandler = new InvalidSessionAccessDeniedHandler(invalidSessionStrategy);
LinkedHashMap<Class<? extends AccessDeniedException>, AccessDeniedHandler> handlers =
new LinkedHashMap<Class<? extends AccessDeniedException>, AccessDeniedHandler>();
handlers.put(MissingCsrfTokenException.class, invalidSessionDeniedHandler);
return new DelegatingAccessDeniedHandler(handlers, defaultAccessDeniedHandler);
}
}

View File

@ -42,6 +42,7 @@ import org.springframework.security.web.context.SecurityContextRepository;
import org.springframework.security.web.savedrequest.NullRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.security.web.session.ConcurrentSessionFilter;
import org.springframework.security.web.session.InvalidSessionStrategy;
import org.springframework.security.web.session.SessionManagementFilter;
import org.springframework.security.web.session.SimpleRedirectInvalidSessionStrategy;
import org.springframework.util.Assert;
@ -66,6 +67,7 @@ import org.springframework.util.Assert;
* <li>{@link RequestCache}</li>
* <li>{@link SecurityContextRepository}</li>
* <li>{@link SessionManagementConfigurer}</li>
* <li>{@link InvalidSessionStrategy}</li>
* </ul>
*
* <h2>Shared Objects Used</h2>
@ -83,6 +85,7 @@ import org.springframework.util.Assert;
public final class SessionManagementConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractHttpConfigurer<SessionManagementConfigurer<H>,H> {
private SessionAuthenticationStrategy sessionFixationAuthenticationStrategy = createDefaultSessionFixationProtectionStrategy();
private SessionAuthenticationStrategy sessionAuthenticationStrategy;
private InvalidSessionStrategy invalidSessionStrategy;
private List<SessionAuthenticationStrategy> sessionAuthenticationStrategies = new ArrayList<SessionAuthenticationStrategy>();
private SessionRegistry sessionRegistry = new SessionRegistryImpl();
private Integer maximumSessions;
@ -365,6 +368,7 @@ public final class SessionManagementConfigurer<H extends HttpSecurityBuilder<H>>
}
}
http.setSharedObject(SessionAuthenticationStrategy.class, getSessionAuthenticationStrategy());
http.setSharedObject(InvalidSessionStrategy.class, getInvalidSessionStrategy());
}
@Override
@ -375,7 +379,7 @@ public final class SessionManagementConfigurer<H extends HttpSecurityBuilder<H>>
sessionManagementFilter.setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler(sessionAuthenticationErrorUrl));
}
if(invalidSessionUrl != null) {
sessionManagementFilter.setInvalidSessionStrategy(new SimpleRedirectInvalidSessionStrategy(invalidSessionUrl));
sessionManagementFilter.setInvalidSessionStrategy(getInvalidSessionStrategy());
}
AuthenticationTrustResolver trustResolver = http.getSharedObject(AuthenticationTrustResolver.class);
if(trustResolver != null) {
@ -391,6 +395,23 @@ public final class SessionManagementConfigurer<H extends HttpSecurityBuilder<H>>
}
}
/**
* Gets the {@link InvalidSessionStrategy} to use. If
* {@link #invalidSessionUrl} is null, returns null otherwise
* {@link SimpleRedirectInvalidSessionStrategy} is used.
*
* @return the {@link InvalidSessionStrategy} to use
*/
InvalidSessionStrategy getInvalidSessionStrategy() {
if(invalidSessionUrl == null) {
return null;
}
if(invalidSessionStrategy == null) {
invalidSessionStrategy = new SimpleRedirectInvalidSessionStrategy(invalidSessionUrl);
}
return invalidSessionStrategy;
}
/**
* Gets the {@link SessionCreationPolicy}. Can not be null.
* @return the {@link SessionCreationPolicy}

View File

@ -15,17 +15,26 @@
*/
package org.springframework.security.config.http;
import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.ManagedMap;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.access.DelegatingAccessDeniedHandler;
import org.springframework.security.web.csrf.CsrfAuthenticationStrategy;
import org.springframework.security.web.csrf.CsrfFilter;
import org.springframework.security.web.csrf.CsrfLogoutHandler;
import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository;
import org.springframework.security.web.csrf.MissingCsrfTokenException;
import org.springframework.security.web.servlet.support.csrf.CsrfRequestDataValueProcessor;
import org.springframework.security.web.session.InvalidSessionAccessDeniedHandler;
import org.springframework.security.web.session.InvalidSessionStrategy;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;
@ -44,6 +53,7 @@ public class CsrfBeanDefinitionParser implements BeanDefinitionParser {
private static final String ATT_REPOSITORY = "token-repository-ref";
private String csrfRepositoryRef;
private BeanDefinition csrfFilter;
public BeanDefinition parse(Element element, ParserContext pc) {
boolean webmvcPresent = ClassUtils.isPresent(DISPATCHER_SERVLET_CLASS_NAME, getClass().getClassLoader());
@ -58,21 +68,64 @@ public class CsrfBeanDefinitionParser implements BeanDefinitionParser {
csrfRepositoryRef = element.getAttribute(ATT_REPOSITORY);
String matcherRef = element.getAttribute(ATT_MATCHER);
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(CsrfFilter.class);
if(!StringUtils.hasText(csrfRepositoryRef)) {
RootBeanDefinition csrfTokenRepository = new RootBeanDefinition(HttpSessionCsrfTokenRepository.class);
csrfRepositoryRef = pc.getReaderContext().generateBeanName(csrfTokenRepository);
pc.registerBeanComponent(new BeanComponentDefinition(csrfTokenRepository, csrfRepositoryRef));
}
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(CsrfFilter.class);
builder.addConstructorArgReference(csrfRepositoryRef);
if(StringUtils.hasText(matcherRef)) {
builder.addPropertyReference("requireCsrfProtectionMatcher", matcherRef);
}
return builder.getBeanDefinition();
csrfFilter = builder.getBeanDefinition();
return csrfFilter;
}
/**
* Populate the AccessDeniedHandler on the {@link CsrfFilter}
*
* @param invalidSessionStrategy the {@link InvalidSessionStrategy} to use
* @param defaultDeniedHandler the {@link AccessDeniedHandler} to use
*/
void initAccessDeniedHandler(BeanDefinition invalidSessionStrategy, BeanMetadataElement defaultDeniedHandler) {
BeanMetadataElement accessDeniedHandler = createAccessDeniedHandler(invalidSessionStrategy, defaultDeniedHandler);
csrfFilter.getPropertyValues().addPropertyValue("accessDeniedHandler", accessDeniedHandler);
}
/**
* Creates the {@link AccessDeniedHandler} from the result of
* {@link #getDefaultAccessDeniedHandler(HttpSecurityBuilder)} and
* {@link #getInvalidSessionStrategy(HttpSecurityBuilder)}. If
* {@link #getInvalidSessionStrategy(HttpSecurityBuilder)} is non-null, then
* a {@link DelegatingAccessDeniedHandler} is used in combination with
* {@link InvalidSessionAccessDeniedHandler} and the
* {@link #getDefaultAccessDeniedHandler(HttpSecurityBuilder)}. Otherwise,
* only {@link #getDefaultAccessDeniedHandler(HttpSecurityBuilder)} is used.
*
* @param invalidSessionStrategy the {@link InvalidSessionStrategy} to use
* @param defaultDeniedHandler the {@link AccessDeniedHandler} to use
*
* @return the {@link BeanMetadataElement} that is the {@link AccessDeniedHandler} to populate on the {@link CsrfFilter}
*/
private BeanMetadataElement createAccessDeniedHandler(BeanDefinition invalidSessionStrategy, BeanMetadataElement defaultDeniedHandler) {
if(invalidSessionStrategy == null) {
return defaultDeniedHandler;
}
ManagedMap<Class<? extends AccessDeniedException>,BeanDefinition> handlers =
new ManagedMap<Class<? extends AccessDeniedException>, BeanDefinition>();
BeanDefinitionBuilder invalidSessionHandlerBldr = BeanDefinitionBuilder.rootBeanDefinition(InvalidSessionAccessDeniedHandler.class);
invalidSessionHandlerBldr.addConstructorArgValue(invalidSessionStrategy);
handlers.put(MissingCsrfTokenException.class, invalidSessionHandlerBldr.getBeanDefinition());
BeanDefinitionBuilder deniedBldr = BeanDefinitionBuilder.rootBeanDefinition(DelegatingAccessDeniedHandler.class);
deniedBldr.addConstructorArgValue(handlers);
deniedBldr.addConstructorArgValue(defaultDeniedHandler);
return deniedBldr.getBeanDefinition();
}
BeanDefinition getCsrfAuthenticationStrategy() {

View File

@ -130,6 +130,10 @@ class HttpConfigurationBuilder {
private BeanMetadataElement csrfLogoutHandler;
private BeanMetadataElement csrfAuthStrategy;
private CsrfBeanDefinitionParser csrfParser;
private BeanDefinition invalidSession;
public HttpConfigurationBuilder(Element element, ParserContext pc,
BeanReference portMapper, BeanReference portResolver, BeanReference authenticationManager) {
this.httpElt = element;
@ -200,8 +204,8 @@ class HttpConfigurationBuilder {
}
void setAccessDeniedHandler(BeanMetadataElement accessDeniedHandler) {
if(csrfFilter != null) {
csrfFilter.getPropertyValues().add("accessDeniedHandler", accessDeniedHandler);
if(csrfParser != null ) {
csrfParser.initAccessDeniedHandler(this.invalidSession, accessDeniedHandler);
}
}
@ -381,7 +385,10 @@ class HttpConfigurationBuilder {
}
if (StringUtils.hasText(invalidSessionUrl)) {
sessionMgmtFilter.addPropertyValue("invalidSessionStrategy", new SimpleRedirectInvalidSessionStrategy(invalidSessionUrl));
BeanDefinitionBuilder invalidSessionBldr = BeanDefinitionBuilder.rootBeanDefinition(SimpleRedirectInvalidSessionStrategy.class);
invalidSessionBldr.addConstructorArgValue(invalidSessionUrl);
invalidSession = invalidSessionBldr.getBeanDefinition();
sessionMgmtFilter.addPropertyValue("invalidSessionStrategy", invalidSession);
}
sessionMgmtFilter.addConstructorArgReference(sessionAuthStratRef);
@ -637,14 +644,16 @@ class HttpConfigurationBuilder {
}
private void createCsrfFilter() {
private CsrfBeanDefinitionParser createCsrfFilter() {
Element elmt = DomUtils.getChildElementByTagName(httpElt, Elements.CSRF);
if (elmt != null) {
CsrfBeanDefinitionParser csrfParser = new CsrfBeanDefinitionParser();
this.csrfFilter = csrfParser.parse(elmt, pc);
csrfParser = new CsrfBeanDefinitionParser();
csrfFilter = csrfParser.parse(elmt, pc);
this.csrfAuthStrategy = csrfParser.getCsrfAuthenticationStrategy();
this.csrfLogoutHandler = csrfParser.getCsrfLogoutHandler();
return csrfParser;
}
return null;
}
BeanMetadataElement getCsrfLogoutHandler() {

View File

@ -18,19 +18,20 @@ package org.springframework.security.config.annotation.web.configurers
import javax.servlet.http.HttpServletResponse
import org.springframework.context.annotation.Configuration
import org.springframework.mock.web.MockHttpServletRequest
import org.springframework.mock.web.MockHttpServletResponse
import org.springframework.security.config.annotation.BaseSpringSpec
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import org.springframework.security.web.access.AccessDeniedHandler
import org.springframework.security.web.csrf.CsrfFilter;
import org.springframework.security.web.csrf.CsrfTokenRepository;
import org.springframework.security.web.servlet.support.csrf.CsrfRequestDataValueProcessor;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.web.servlet.support.RequestDataValueProcessor;
import org.springframework.security.web.csrf.CsrfFilter
import org.springframework.security.web.csrf.CsrfTokenRepository
import org.springframework.security.web.util.matcher.RequestMatcher
import org.springframework.web.servlet.support.RequestDataValueProcessor
import spock.lang.Unroll;
import spock.lang.Unroll
/**
*
@ -100,6 +101,37 @@ class CsrfConfigurerTests extends BaseSpringSpec {
}
}
def "SEC-2422: csrf expire CSRF token and session-management invalid-session-url"() {
setup:
loadConfig(InvalidSessionUrlConfig)
request.session.clearAttributes()
request.setParameter("_csrf","abc")
request.method = "POST"
when: "No existing expected CsrfToken (session times out) and a POST"
springSecurityFilterChain.doFilter(request,response,chain)
then: "sent to the session timeout page page"
response.status == HttpServletResponse.SC_MOVED_TEMPORARILY
response.redirectedUrl == "/error/sessionError"
when: "Existing expected CsrfToken and a POST (invalid token provided)"
response = new MockHttpServletResponse()
request = new MockHttpServletRequest(session: request.session, method:'POST')
springSecurityFilterChain.doFilter(request,response,chain)
then: "Access Denied occurs"
response.status == HttpServletResponse.SC_FORBIDDEN
}
@Configuration
@EnableWebSecurity
static class InvalidSessionUrlConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().and()
.sessionManagement()
.invalidSessionUrl("/error/sessionError")
}
}
def "csrf requireCsrfProtectionMatcher"() {
setup:
RequireCsrfProtectionMatcherConfig.matcher = Mock(RequestMatcher)

View File

@ -174,6 +174,28 @@ class CsrfConfigTests extends AbstractHttpConfigTests {
response.redirectedUrl == "http://localhost/some-url"
}
def "SEC-2422: csrf expire CSRF token and session-management invalid-session-url"() {
setup:
httpAutoConfig {
'csrf'()
'session-management'('invalid-session-url': '/error/sessionError')
}
createAppContext()
request.setParameter("_csrf","abc")
request.method = "POST"
when: "No existing expected CsrfToken (session times out) and a POST"
springSecurityFilterChain.doFilter(request,response,chain)
then: "sent to the session timeout page page"
response.status == HttpServletResponse.SC_MOVED_TEMPORARILY
response.redirectedUrl == "/error/sessionError"
when: "Existing expected CsrfToken and a POST (invalid token provided)"
response = new MockHttpServletResponse()
request = new MockHttpServletRequest(session: request.session, method:'POST')
springSecurityFilterChain.doFilter(request,response,chain)
then: "Access Denied occurs"
response.status == HttpServletResponse.SC_FORBIDDEN
}
def "csrf requireCsrfProtectionMatcher"() {
setup:
httpAutoConfig {

View File

@ -0,0 +1,81 @@
/*
* 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.web.access;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map.Entry;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.util.Assert;
/**
* An {@link AccessDeniedHandler} that delegates to other
* {@link AccessDeniedHandler} instances based upon the type of
* {@link AccessDeniedException} passed into
* {@link #handle(HttpServletRequest, HttpServletResponse, AccessDeniedException)}.
*
* @author Rob Winch
* @since 3.2
*
*/
public final class DelegatingAccessDeniedHandler implements AccessDeniedHandler {
private final LinkedHashMap<Class<? extends AccessDeniedException>, AccessDeniedHandler> handlers;
private final AccessDeniedHandler defaultHander;
/**
* Creates a new instance
*
* @param handlers
* a map of the {@link AccessDeniedException} class to the
* {@link AccessDeniedHandler} that should be used. Each is
* considered in the order they are specified and only the first
* {@link AccessDeniedHandler} is ued.
* @param defaultHander
* the default {@link AccessDeniedHandler} that should be used if
* none of the handlers matches.
*/
public DelegatingAccessDeniedHandler(
LinkedHashMap<Class<? extends AccessDeniedException>, AccessDeniedHandler> handlers,
AccessDeniedHandler defaultHander) {
Assert.notEmpty(handlers, "handlers cannot be null or empty");
Assert.notNull(defaultHander, "defaultHandler cannot be null");
this.handlers = handlers;
this.defaultHander = defaultHander;
}
public void handle(HttpServletRequest request,
HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException,
ServletException {
for(Entry<Class<? extends AccessDeniedException>, AccessDeniedHandler> entry : handlers.entrySet()) {
Class<? extends AccessDeniedException> handlerClass = entry.getKey();
if(handlerClass.isAssignableFrom(accessDeniedException.getClass())) {
AccessDeniedHandler handler = entry.getValue();
handler.handle(request, response, accessDeniedException);
return;
}
}
defaultHander.handle(request, response, accessDeniedException);
}
}

View File

@ -0,0 +1,32 @@
/*
* 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.web.csrf;
import org.springframework.security.access.AccessDeniedException;
/**
* Thrown when an invalid or missing {@link CsrfToken} is found in the HttpServletRequest
*
* @author Rob Winch
* @since 3.2
*/
@SuppressWarnings("serial")
public class CsrfException extends AccessDeniedException {
public CsrfException(String message) {
super(message);
}
}

View File

@ -73,7 +73,8 @@ public final class CsrfFilter extends OncePerRequestFilter {
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
CsrfToken csrfToken = tokenRepository.loadToken(request);
if(csrfToken == null) {
final boolean missingToken = csrfToken == null;
if(missingToken) {
CsrfToken generatedToken = tokenRepository.generateToken(request);
csrfToken = new SaveOnAccessCsrfToken(tokenRepository, request, response, generatedToken);
}
@ -93,7 +94,11 @@ public final class CsrfFilter extends OncePerRequestFilter {
if(logger.isDebugEnabled()) {
logger.debug("Invalid CSRF token found for " + UrlUtils.buildFullRequestUrl(request));
}
accessDeniedHandler.handle(request, response, new InvalidCsrfTokenException(csrfToken, actualToken));
if(missingToken) {
accessDeniedHandler.handle(request, response, new MissingCsrfTokenException(actualToken));
} else {
accessDeniedHandler.handle(request, response, new InvalidCsrfTokenException(csrfToken, actualToken));
}
return;
}

View File

@ -15,17 +15,17 @@
*/
package org.springframework.security.web.csrf;
import org.springframework.security.access.AccessDeniedException;
import javax.servlet.http.HttpServletRequest;
/**
* Thrown when an invalid or missing {@link CsrfToken} is found in the HttpServletRequest
* Thrown when an expected {@link CsrfToken} exists, but it does not match the
* value present on the {@link HttpServletRequest}
*
* @author Rob Winch
* @since 3.2
*/
@SuppressWarnings("serial")
public class InvalidCsrfTokenException extends AccessDeniedException {
public class InvalidCsrfTokenException extends CsrfException {
/**
* @param msg
@ -36,5 +36,4 @@ public class InvalidCsrfTokenException extends AccessDeniedException {
+ expectedAccessToken.getParameterName() + "' or header '"
+ expectedAccessToken.getHeaderName() + "'.");
}
}
}

View File

@ -0,0 +1,30 @@
/*
* 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.web.csrf;
/**
* Thrown when no expected {@link CsrfToken} is found but is required.
*
* @author Rob Winch
* @since 3.2
*/
@SuppressWarnings("serial")
public class MissingCsrfTokenException extends CsrfException {
public MissingCsrfTokenException(String actualToken) {
super("Expected CSRF token not found. Has your session expired?");
}
}

View File

@ -0,0 +1,53 @@
/*
* 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.web.session;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.util.Assert;
/**
* An adapter of {@link InvalidSessionStrategy} to {@link AccessDeniedHandler}
*
* @author Rob Winch
* @since 3.2
*/
public final class InvalidSessionAccessDeniedHandler implements AccessDeniedHandler {
private final InvalidSessionStrategy invalidSessionStrategy;
/**
* Creates a new instance
* @param invalidSessionStrategy the {@link InvalidSessionStrategy} to delegate to
*/
public InvalidSessionAccessDeniedHandler(
InvalidSessionStrategy invalidSessionStrategy) {
Assert.notNull(invalidSessionStrategy, "invalidSessionStrategy cannot be null");
this.invalidSessionStrategy = invalidSessionStrategy;
}
public void handle(HttpServletRequest request,
HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException,
ServletException {
invalidSessionStrategy.onInvalidSessionDetected(request, response);
}
}

View File

@ -0,0 +1,84 @@
/*
* 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.web.access;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import java.util.LinkedHashMap;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.csrf.CsrfException;
import org.springframework.security.web.csrf.InvalidCsrfTokenException;
import org.springframework.security.web.csrf.MissingCsrfTokenException;
@RunWith(MockitoJUnitRunner.class)
public class DelegatingAccessDeniedHandlerTests {
@Mock
private AccessDeniedHandler handler1;
@Mock
private AccessDeniedHandler handler2;
@Mock
private AccessDeniedHandler handler3;
@Mock
private HttpServletRequest request;
@Mock
private HttpServletResponse response;
private LinkedHashMap<Class<? extends AccessDeniedException>,AccessDeniedHandler> handlers;
private DelegatingAccessDeniedHandler handler;
@Before
public void setup() {
handlers = new LinkedHashMap<Class<? extends AccessDeniedException>, AccessDeniedHandler>();
}
@Test
public void moreSpecificDoesNotInvokeLessSpecific() throws Exception {
handlers.put(CsrfException.class, handler1);
handler = new DelegatingAccessDeniedHandler(handlers, handler3);
AccessDeniedException accessDeniedException = new AccessDeniedException("");
handler.handle(request, response, accessDeniedException);
verify(handler1,never()).handle(any(HttpServletRequest.class), any(HttpServletResponse.class), any(AccessDeniedException.class));
verify(handler3).handle(request, response, accessDeniedException);
}
@Test
public void matchesDoesNotInvokeDefault() throws Exception {
handlers.put(InvalidCsrfTokenException.class, handler1);
handlers.put(MissingCsrfTokenException.class, handler2);
handler = new DelegatingAccessDeniedHandler(handlers, handler3);
AccessDeniedException accessDeniedException = new MissingCsrfTokenException("123");
handler.handle(request, response, accessDeniedException);
verify(handler1,never()).handle(any(HttpServletRequest.class), any(HttpServletResponse.class), any(AccessDeniedException.class));
verify(handler2).handle(request, response, accessDeniedException);
verify(handler3,never()).handle(any(HttpServletRequest.class), any(HttpServletResponse.class), any(AccessDeniedException.class));
}
}