diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/CsrfConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/CsrfConfigurer.java
index 993c8c420d..2ed4d28dc7 100644
--- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/CsrfConfigurer.java
+++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/CsrfConfigurer.java
@@ -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;
*
* {@link ExceptionHandlingConfigurer#accessDeniedHandler(AccessDeniedHandler)}
* is used to determine how to handle CSRF attempts
+ * {@link InvalidSessionStrategy}
*
*
* @author Rob Winch
@@ -100,12 +109,9 @@ public final class CsrfConfigurer> extends Abst
if(requireCsrfProtectionMatcher != null) {
filter.setRequireCsrfProtectionMatcher(requireCsrfProtectionMatcher);
}
- ExceptionHandlingConfigurer 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 logoutConfigurer = http.getConfigurer(LogoutConfigurer.class);
if(logoutConfigurer != null) {
@@ -118,4 +124,70 @@ public final class CsrfConfigurer> 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 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 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, AccessDeniedHandler> handlers =
+ new LinkedHashMap, AccessDeniedHandler>();
+ handlers.put(MissingCsrfTokenException.class, invalidSessionDeniedHandler);
+ return new DelegatingAccessDeniedHandler(handlers, defaultAccessDeniedHandler);
+ }
}
\ No newline at end of file
diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurer.java
index 6328fcf631..78d2058d23 100644
--- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurer.java
+++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurer.java
@@ -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;
* {@link RequestCache}
* {@link SecurityContextRepository}
* {@link SessionManagementConfigurer}
+ * {@link InvalidSessionStrategy}
*
*
* Shared Objects Used
@@ -83,6 +85,7 @@ import org.springframework.util.Assert;
public final class SessionManagementConfigurer> extends AbstractHttpConfigurer,H> {
private SessionAuthenticationStrategy sessionFixationAuthenticationStrategy = createDefaultSessionFixationProtectionStrategy();
private SessionAuthenticationStrategy sessionAuthenticationStrategy;
+ private InvalidSessionStrategy invalidSessionStrategy;
private List sessionAuthenticationStrategies = new ArrayList();
private SessionRegistry sessionRegistry = new SessionRegistryImpl();
private Integer maximumSessions;
@@ -365,6 +368,7 @@ public final class SessionManagementConfigurer>
}
}
http.setSharedObject(SessionAuthenticationStrategy.class, getSessionAuthenticationStrategy());
+ http.setSharedObject(InvalidSessionStrategy.class, getInvalidSessionStrategy());
}
@Override
@@ -375,7 +379,7 @@ public final class SessionManagementConfigurer>
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>
}
}
+ /**
+ * 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}
diff --git a/config/src/main/java/org/springframework/security/config/http/CsrfBeanDefinitionParser.java b/config/src/main/java/org/springframework/security/config/http/CsrfBeanDefinitionParser.java
index 2fa1b9d9fa..2b5aa6282b 100644
--- a/config/src/main/java/org/springframework/security/config/http/CsrfBeanDefinitionParser.java
+++ b/config/src/main/java/org/springframework/security/config/http/CsrfBeanDefinitionParser.java
@@ -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,BeanDefinition> handlers =
+ new ManagedMap, 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() {
diff --git a/config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java b/config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java
index f6f789e74b..c4ff37ffd8 100644
--- a/config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java
+++ b/config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java
@@ -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() {
diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/CsrfConfigurerTests.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/CsrfConfigurerTests.groovy
index 564d3fabd1..8aba928683 100644
--- a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/CsrfConfigurerTests.groovy
+++ b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/CsrfConfigurerTests.groovy
@@ -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)
diff --git a/config/src/test/groovy/org/springframework/security/config/http/CsrfConfigTests.groovy b/config/src/test/groovy/org/springframework/security/config/http/CsrfConfigTests.groovy
index 5360de28d8..14cd0a74fe 100644
--- a/config/src/test/groovy/org/springframework/security/config/http/CsrfConfigTests.groovy
+++ b/config/src/test/groovy/org/springframework/security/config/http/CsrfConfigTests.groovy
@@ -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 {
diff --git a/web/src/main/java/org/springframework/security/web/access/DelegatingAccessDeniedHandler.java b/web/src/main/java/org/springframework/security/web/access/DelegatingAccessDeniedHandler.java
new file mode 100644
index 0000000000..992c874da0
--- /dev/null
+++ b/web/src/main/java/org/springframework/security/web/access/DelegatingAccessDeniedHandler.java
@@ -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, 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, 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, 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);
+ }
+
+}
diff --git a/web/src/main/java/org/springframework/security/web/csrf/CsrfException.java b/web/src/main/java/org/springframework/security/web/csrf/CsrfException.java
new file mode 100644
index 0000000000..f21c50c208
--- /dev/null
+++ b/web/src/main/java/org/springframework/security/web/csrf/CsrfException.java
@@ -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);
+ }
+}
\ No newline at end of file
diff --git a/web/src/main/java/org/springframework/security/web/csrf/CsrfFilter.java b/web/src/main/java/org/springframework/security/web/csrf/CsrfFilter.java
index 6c5d74517a..683d20d4ab 100644
--- a/web/src/main/java/org/springframework/security/web/csrf/CsrfFilter.java
+++ b/web/src/main/java/org/springframework/security/web/csrf/CsrfFilter.java
@@ -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;
}
diff --git a/web/src/main/java/org/springframework/security/web/csrf/InvalidCsrfTokenException.java b/web/src/main/java/org/springframework/security/web/csrf/InvalidCsrfTokenException.java
index 38faff7e35..3b8fc0ef0d 100644
--- a/web/src/main/java/org/springframework/security/web/csrf/InvalidCsrfTokenException.java
+++ b/web/src/main/java/org/springframework/security/web/csrf/InvalidCsrfTokenException.java
@@ -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() + "'.");
}
-
-}
+}
\ No newline at end of file
diff --git a/web/src/main/java/org/springframework/security/web/csrf/MissingCsrfTokenException.java b/web/src/main/java/org/springframework/security/web/csrf/MissingCsrfTokenException.java
new file mode 100644
index 0000000000..855a0201de
--- /dev/null
+++ b/web/src/main/java/org/springframework/security/web/csrf/MissingCsrfTokenException.java
@@ -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?");
+ }
+}
\ No newline at end of file
diff --git a/web/src/main/java/org/springframework/security/web/session/InvalidSessionAccessDeniedHandler.java b/web/src/main/java/org/springframework/security/web/session/InvalidSessionAccessDeniedHandler.java
new file mode 100644
index 0000000000..b8f7b1d82b
--- /dev/null
+++ b/web/src/main/java/org/springframework/security/web/session/InvalidSessionAccessDeniedHandler.java
@@ -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);
+ }
+}
\ No newline at end of file
diff --git a/web/src/test/java/org/springframework/security/web/access/DelegatingAccessDeniedHandlerTests.java b/web/src/test/java/org/springframework/security/web/access/DelegatingAccessDeniedHandlerTests.java
new file mode 100644
index 0000000000..6d8e59bb63
--- /dev/null
+++ b/web/src/test/java/org/springframework/security/web/access/DelegatingAccessDeniedHandlerTests.java
@@ -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,AccessDeniedHandler> handlers;
+
+ private DelegatingAccessDeniedHandler handler;
+
+ @Before
+ public void setup() {
+ handlers = new LinkedHashMap, 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));
+ }
+}