SEC-2422: Session timeout detection with CSRF protection
This commit is contained in:
parent
00d668dc5c
commit
7f714ebb23
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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() + "'.");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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?");
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue