From 186601eae9fe3a5a2fea69bc1e1fd3365decc90e Mon Sep 17 00:00:00 2001
From: Steve Riesenberg
Date: Thu, 11 Aug 2022 16:29:56 -0500
Subject: [PATCH] Add new interfaces for CSRF request processing
Issue gh-4001
Issue gh-11456
---
.../web/configurers/CsrfConfigurer.java | 47 ++++--
.../config/http/CsrfBeanDefinitionParser.java | 20 ++-
.../security/config/spring-security-5.8.rnc | 9 +-
.../security/config/spring-security-5.8.xsd | 19 ++-
.../DeferHttpSessionJavaConfigTests.java | 5 +-
.../web/configurers/CsrfConfigurerTests.java | 90 +++++++++++-
.../security/config/http/CsrfConfigTests.java | 2 +-
.../CsrfConfigTests-WithRequestAttrName.xml | 5 +-
.../http/DeferHttpSessionTests-Explicit.xml | 4 +-
.../servlet/appendix/namespace/http.adoc | 11 +-
.../web/csrf/CsrfAuthenticationStrategy.java | 17 ++-
.../security/web/csrf/CsrfFilter.java | 55 ++++---
.../CsrfTokenRequestAttributeHandler.java | 45 ++++++
.../web/csrf/CsrfTokenRequestProcessor.java | 75 ++++++++++
.../web/csrf/CsrfTokenRequestResolver.java | 42 ++++++
.../csrf/CsrfAuthenticationStrategyTests.java | 21 +++
.../security/web/csrf/CsrfFilterTests.java | 30 +++-
.../csrf/CsrfTokenRequestProcessorTests.java | 134 ++++++++++++++++++
18 files changed, 572 insertions(+), 59 deletions(-)
create mode 100644 web/src/main/java/org/springframework/security/web/csrf/CsrfTokenRequestAttributeHandler.java
create mode 100644 web/src/main/java/org/springframework/security/web/csrf/CsrfTokenRequestProcessor.java
create mode 100644 web/src/main/java/org/springframework/security/web/csrf/CsrfTokenRequestResolver.java
create mode 100644 web/src/test/java/org/springframework/security/web/csrf/CsrfTokenRequestProcessorTests.java
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 3831190e906..1b755e315de 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2022 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.
@@ -36,6 +36,8 @@
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.CsrfTokenRequestAttributeHandler;
+import org.springframework.security.web.csrf.CsrfTokenRequestResolver;
import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository;
import org.springframework.security.web.csrf.LazyCsrfTokenRepository;
import org.springframework.security.web.csrf.MissingCsrfTokenException;
@@ -89,7 +91,9 @@ public final class CsrfConfigurer>
private SessionAuthenticationStrategy sessionAuthenticationStrategy;
- private String csrfRequestAttributeName;
+ private CsrfTokenRequestAttributeHandler requestAttributeHandler;
+
+ private CsrfTokenRequestResolver requestResolver;
private final ApplicationContext context;
@@ -127,12 +131,25 @@ public CsrfConfigurer requireCsrfProtectionMatcher(RequestMatcher requireCsrf
}
/**
- * Sets the {@link CsrfFilter#setCsrfRequestAttributeName(String)}
- * @param csrfRequestAttributeName the attribute name to set the CsrfToken on.
- * @return the {@link CsrfConfigurer} for further customizations.
+ * Specify a {@link CsrfTokenRequestAttributeHandler} to use for making the
+ * {@code CsrfToken} available as a request attribute.
+ * @param requestAttributeHandler the {@link CsrfTokenRequestAttributeHandler} to use
+ * @return the {@link CsrfConfigurer} for further customizations
+ */
+ public CsrfConfigurer csrfTokenRequestAttributeHandler(
+ CsrfTokenRequestAttributeHandler requestAttributeHandler) {
+ this.requestAttributeHandler = requestAttributeHandler;
+ return this;
+ }
+
+ /**
+ * Specify a {@link CsrfTokenRequestResolver} to use for resolving the token value
+ * from the request.
+ * @param requestResolver the {@link CsrfTokenRequestResolver} to use
+ * @return the {@link CsrfConfigurer} for further customizations
*/
- public CsrfConfigurer csrfRequestAttributeName(String csrfRequestAttributeName) {
- this.csrfRequestAttributeName = csrfRequestAttributeName;
+ public CsrfConfigurer csrfTokenRequestResolver(CsrfTokenRequestResolver requestResolver) {
+ this.requestResolver = requestResolver;
return this;
}
@@ -214,9 +231,6 @@ public CsrfConfigurer sessionAuthenticationStrategy(
@Override
public void configure(H http) {
CsrfFilter filter = new CsrfFilter(this.csrfTokenRepository);
- if (this.csrfRequestAttributeName != null) {
- filter.setCsrfRequestAttributeName(this.csrfRequestAttributeName);
- }
RequestMatcher requireCsrfProtectionMatcher = getRequireCsrfProtectionMatcher();
if (requireCsrfProtectionMatcher != null) {
filter.setRequireCsrfProtectionMatcher(requireCsrfProtectionMatcher);
@@ -233,6 +247,12 @@ public void configure(H http) {
if (sessionConfigurer != null) {
sessionConfigurer.addSessionAuthenticationStrategy(getSessionAuthenticationStrategy());
}
+ if (this.requestAttributeHandler != null) {
+ filter.setRequestAttributeHandler(this.requestAttributeHandler);
+ }
+ if (this.requestResolver != null) {
+ filter.setRequestResolver(this.requestResolver);
+ }
filter = postProcess(filter);
http.addFilter(filter);
}
@@ -321,7 +341,12 @@ private SessionAuthenticationStrategy getSessionAuthenticationStrategy() {
if (this.sessionAuthenticationStrategy != null) {
return this.sessionAuthenticationStrategy;
}
- return new CsrfAuthenticationStrategy(this.csrfTokenRepository);
+ CsrfAuthenticationStrategy csrfAuthenticationStrategy = new CsrfAuthenticationStrategy(
+ this.csrfTokenRepository);
+ if (this.requestAttributeHandler != null) {
+ csrfAuthenticationStrategy.setRequestAttributeHandler(this.requestAttributeHandler);
+ }
+ return csrfAuthenticationStrategy;
}
/**
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 495a2ddd2ad..c76d4c4d11e 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
@@ -67,13 +67,13 @@ public class CsrfBeanDefinitionParser implements BeanDefinitionParser {
private static final String DISPATCHER_SERVLET_CLASS_NAME = "org.springframework.web.servlet.DispatcherServlet";
- private static final String ATT_REQUEST_ATTRIBUTE_NAME = "request-attribute-name";
-
private static final String ATT_MATCHER = "request-matcher-ref";
private static final String ATT_REPOSITORY = "token-repository-ref";
- private String requestAttributeName;
+ private static final String ATT_REQUEST_ATTRIBUTE_HANDLER = "request-attribute-handler-ref";
+
+ private static final String ATT_REQUEST_RESOLVER = "request-resolver-ref";
private String csrfRepositoryRef;
@@ -81,6 +81,10 @@ public class CsrfBeanDefinitionParser implements BeanDefinitionParser {
private String requestMatcherRef;
+ private String requestAttributeHandlerRef;
+
+ private String requestResolverRef;
+
@Override
public BeanDefinition parse(Element element, ParserContext pc) {
boolean disabled = element != null && "true".equals(element.getAttribute("disabled"));
@@ -98,8 +102,9 @@ public BeanDefinition parse(Element element, ParserContext pc) {
}
if (element != null) {
this.csrfRepositoryRef = element.getAttribute(ATT_REPOSITORY);
- this.requestAttributeName = element.getAttribute(ATT_REQUEST_ATTRIBUTE_NAME);
this.requestMatcherRef = element.getAttribute(ATT_MATCHER);
+ this.requestAttributeHandlerRef = element.getAttribute(ATT_REQUEST_ATTRIBUTE_HANDLER);
+ this.requestResolverRef = element.getAttribute(ATT_REQUEST_RESOLVER);
}
if (!StringUtils.hasText(this.csrfRepositoryRef)) {
RootBeanDefinition csrfTokenRepository = new RootBeanDefinition(HttpSessionCsrfTokenRepository.class);
@@ -115,8 +120,11 @@ public BeanDefinition parse(Element element, ParserContext pc) {
if (StringUtils.hasText(this.requestMatcherRef)) {
builder.addPropertyReference("requireCsrfProtectionMatcher", this.requestMatcherRef);
}
- if (StringUtils.hasText(this.requestAttributeName)) {
- builder.addPropertyValue("csrfRequestAttributeName", this.requestAttributeName);
+ if (StringUtils.hasText(this.requestAttributeHandlerRef)) {
+ builder.addPropertyReference("requestAttributeHandler", this.requestAttributeHandlerRef);
+ }
+ if (StringUtils.hasText(this.requestResolverRef)) {
+ builder.addPropertyReference("requestResolver", this.requestResolverRef);
}
this.csrfFilter = builder.getBeanDefinition();
return this.csrfFilter;
diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-5.8.rnc b/config/src/main/resources/org/springframework/security/config/spring-security-5.8.rnc
index bc0c0557628..3f48f7bcd48 100644
--- a/config/src/main/resources/org/springframework/security/config/spring-security-5.8.rnc
+++ b/config/src/main/resources/org/springframework/security/config/spring-security-5.8.rnc
@@ -1142,15 +1142,18 @@ csrf =
csrf-options.attlist &=
## Specifies if csrf protection should be disabled. Default false (i.e. CSRF protection is enabled).
attribute disabled {xsd:boolean}?
-csrf-options.attlist &=
- ## The request attribute name the CsrfToken is set on. Default is to set to CsrfToken.parameterName
- attribute request-attribute-name { xsd:token }?
csrf-options.attlist &=
## The RequestMatcher instance to be used to determine if CSRF should be applied. Default is any HTTP method except "GET", "TRACE", "HEAD", "OPTIONS"
attribute request-matcher-ref { xsd:token }?
csrf-options.attlist &=
## The CsrfTokenRepository to use. The default is HttpSessionCsrfTokenRepository wrapped by LazyCsrfTokenRepository.
attribute token-repository-ref { xsd:token }?
+csrf-options.attlist &=
+ ## The CsrfTokenRequestAttributeHandler to use. The default is CsrfTokenRequestProcessor.
+ attribute request-attribute-handler-ref { xsd:token }?
+csrf-options.attlist &=
+ ## The CsrfTokenRequestResolver to use. The default is CsrfTokenRequestProcessor.
+ attribute request-resolver-ref { xsd:token }?
headers =
## Element for configuration of the HeaderWritersFilter. Enables easy setting for the X-Frame-Options, X-XSS-Protection and X-Content-Type-Options headers.
diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-5.8.xsd b/config/src/main/resources/org/springframework/security/config/spring-security-5.8.xsd
index 37bda48acdf..bef39a7c620 100644
--- a/config/src/main/resources/org/springframework/security/config/spring-security-5.8.xsd
+++ b/config/src/main/resources/org/springframework/security/config/spring-security-5.8.xsd
@@ -3235,13 +3235,6 @@
-
-
- The request attribute name the CsrfToken is set on. Default is to set to
- CsrfToken.parameterName
-
-
-
The RequestMatcher instance to be used to determine if CSRF should be applied. Default is
@@ -3256,6 +3249,18 @@
+
+
+ The CsrfTokenRequestAttributeHandler to use. The default is CsrfTokenRequestProcessor.
+
+
+
+
+
+ The CsrfTokenRequestResolver to use. The default is CsrfTokenRequestProcessor.
+
+
+
diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configuration/DeferHttpSessionJavaConfigTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configuration/DeferHttpSessionJavaConfigTests.java
index 1f24cf24ed2..b6cf0d68b57 100644
--- a/config/src/test/java/org/springframework/security/config/annotation/web/configuration/DeferHttpSessionJavaConfigTests.java
+++ b/config/src/test/java/org/springframework/security/config/annotation/web/configuration/DeferHttpSessionJavaConfigTests.java
@@ -33,6 +33,7 @@
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.FilterChainProxy;
+import org.springframework.security.web.csrf.CsrfTokenRequestProcessor;
import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository;
import org.springframework.security.web.csrf.LazyCsrfTokenRepository;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
@@ -84,6 +85,8 @@ DefaultSecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
csrfRepository.setDeferLoadToken(true);
HttpSessionRequestCache requestCache = new HttpSessionRequestCache();
requestCache.setMatchingRequestParameterName("continue");
+ CsrfTokenRequestProcessor requestAttributeHandler = new CsrfTokenRequestProcessor();
+ requestAttributeHandler.setCsrfRequestAttributeName("_csrf");
// @formatter:off
http
.requestCache((cache) -> cache
@@ -99,7 +102,7 @@ DefaultSecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
.requireExplicitAuthenticationStrategy(true)
)
.csrf((csrf) -> csrf
- .csrfRequestAttributeName("_csrf")
+ .csrfTokenRequestAttributeHandler(requestAttributeHandler)
.csrfTokenRepository(csrfRepository)
);
// @formatter:on
diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/CsrfConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/CsrfConfigurerTests.java
index bc431b6afbb..3145696f727 100644
--- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/CsrfConfigurerTests.java
+++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/CsrfConfigurerTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2022 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.
@@ -30,6 +30,7 @@
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.mock.web.MockHttpSession;
+import org.springframework.security.config.Customizer;
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;
@@ -38,9 +39,12 @@
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.PasswordEncodedUser;
+import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
+import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.csrf.CsrfTokenRepository;
+import org.springframework.security.web.csrf.CsrfTokenRequestProcessor;
import org.springframework.security.web.csrf.DefaultCsrfToken;
import org.springframework.security.web.firewall.StrictHttpFirewall;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
@@ -55,12 +59,16 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+import static org.hamcrest.Matchers.containsString;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.springframework.security.config.Customizer.withDefaults;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
@@ -74,6 +82,7 @@
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.request;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@@ -84,6 +93,7 @@
* @author Eleftheria Stein
* @author Michael Vitz
* @author Sam Simmons
+ * @author Steve Riesenberg
*/
@ExtendWith(SpringTestContextExtension.class)
public class CsrfConfigurerTests {
@@ -407,6 +417,47 @@ public void csrfAuthenticationStrategyConfiguredThenStrategyUsed() throws Except
any(HttpServletRequest.class), any(HttpServletResponse.class));
}
+ @Test
+ public void getLoginWhenCsrfTokenRequestProcessorSetThenRespondsWithNormalCsrfToken() throws Exception {
+ CsrfTokenRepository csrfTokenRepository = mock(CsrfTokenRepository.class);
+ CsrfToken csrfToken = new DefaultCsrfToken("X-CSRF-TOKEN", "_csrf", "token");
+ given(csrfTokenRepository.generateToken(any(HttpServletRequest.class))).willReturn(csrfToken);
+ CsrfTokenRequestProcessorConfig.REPO = csrfTokenRepository;
+ CsrfTokenRequestProcessorConfig.PROCESSOR = new CsrfTokenRequestProcessor();
+ this.spring.register(CsrfTokenRequestProcessorConfig.class, BasicController.class).autowire();
+ this.mvc.perform(get("/login")).andExpect(status().isOk())
+ .andExpect(content().string(containsString(csrfToken.getToken())));
+ verify(csrfTokenRepository).loadToken(any(HttpServletRequest.class));
+ verify(csrfTokenRepository).generateToken(any(HttpServletRequest.class));
+ verify(csrfTokenRepository).saveToken(eq(csrfToken), any(HttpServletRequest.class),
+ any(HttpServletResponse.class));
+ verifyNoMoreInteractions(csrfTokenRepository);
+ }
+
+ @Test
+ public void loginWhenCsrfTokenRequestProcessorSetAndNormalCsrfTokenThenSuccess() throws Exception {
+ CsrfToken csrfToken = new DefaultCsrfToken("X-CSRF-TOKEN", "_csrf", "token");
+ CsrfTokenRepository csrfTokenRepository = mock(CsrfTokenRepository.class);
+ given(csrfTokenRepository.loadToken(any(HttpServletRequest.class))).willReturn(csrfToken);
+ given(csrfTokenRepository.generateToken(any(HttpServletRequest.class))).willReturn(csrfToken);
+ CsrfTokenRequestProcessorConfig.REPO = csrfTokenRepository;
+ CsrfTokenRequestProcessorConfig.PROCESSOR = new CsrfTokenRequestProcessor();
+ this.spring.register(CsrfTokenRequestProcessorConfig.class, BasicController.class).autowire();
+ // @formatter:off
+ MockHttpServletRequestBuilder loginRequest = post("/login")
+ .header(csrfToken.getHeaderName(), csrfToken.getToken())
+ .param("username", "user")
+ .param("password", "password");
+ // @formatter:on
+ this.mvc.perform(loginRequest).andExpect(redirectedUrl("/"));
+ verify(csrfTokenRepository, times(2)).loadToken(any(HttpServletRequest.class));
+ verify(csrfTokenRepository).saveToken(isNull(), any(HttpServletRequest.class), any(HttpServletResponse.class));
+ verify(csrfTokenRepository).generateToken(any(HttpServletRequest.class));
+ verify(csrfTokenRepository).saveToken(eq(csrfToken), any(HttpServletRequest.class),
+ any(HttpServletResponse.class));
+ verifyNoMoreInteractions(csrfTokenRepository);
+ }
+
@Configuration
static class AllowHttpMethodsFirewallConfig {
@@ -748,6 +799,43 @@ protected void configure(AuthenticationManagerBuilder auth) throws Exception {
}
+ @Configuration
+ @EnableWebSecurity
+ static class CsrfTokenRequestProcessorConfig {
+
+ static CsrfTokenRepository REPO;
+
+ static CsrfTokenRequestProcessor PROCESSOR;
+
+ @Bean
+ SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
+ // @formatter:off
+ http
+ .authorizeHttpRequests((authorize) -> authorize
+ .anyRequest().authenticated()
+ )
+ .formLogin(Customizer.withDefaults())
+ .csrf((csrf) -> csrf
+ .csrfTokenRepository(REPO)
+ .csrfTokenRequestAttributeHandler(PROCESSOR)
+ .csrfTokenRequestResolver(PROCESSOR)
+ );
+ // @formatter:on
+
+ return http.build();
+ }
+
+ @Autowired
+ void configure(AuthenticationManagerBuilder auth) throws Exception {
+ // @formatter:off
+ auth
+ .inMemoryAuthentication()
+ .withUser(PasswordEncodedUser.user());
+ // @formatter:on
+ }
+
+ }
+
@RestController
static class BasicController {
diff --git a/config/src/test/java/org/springframework/security/config/http/CsrfConfigTests.java b/config/src/test/java/org/springframework/security/config/http/CsrfConfigTests.java
index a62bfae26dd..e9220895fbb 100644
--- a/config/src/test/java/org/springframework/security/config/http/CsrfConfigTests.java
+++ b/config/src/test/java/org/springframework/security/config/http/CsrfConfigTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2018 the original author or authors.
+ * Copyright 2002-2022 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.
diff --git a/config/src/test/resources/org/springframework/security/config/http/CsrfConfigTests-WithRequestAttrName.xml b/config/src/test/resources/org/springframework/security/config/http/CsrfConfigTests-WithRequestAttrName.xml
index 4f6c27248ba..541f66453fe 100644
--- a/config/src/test/resources/org/springframework/security/config/http/CsrfConfigTests-WithRequestAttrName.xml
+++ b/config/src/test/resources/org/springframework/security/config/http/CsrfConfigTests-WithRequestAttrName.xml
@@ -16,14 +16,17 @@
-->
-
+
+
diff --git a/config/src/test/resources/org/springframework/security/config/http/DeferHttpSessionTests-Explicit.xml b/config/src/test/resources/org/springframework/security/config/http/DeferHttpSessionTests-Explicit.xml
index 9be33976baa..2efb29d03ec 100644
--- a/config/src/test/resources/org/springframework/security/config/http/DeferHttpSessionTests-Explicit.xml
+++ b/config/src/test/resources/org/springframework/security/config/http/DeferHttpSessionTests-Explicit.xml
@@ -30,7 +30,7 @@
security-context-explicit-save="true"
use-authorization-manager="true">
-
@@ -42,5 +42,7 @@
+
diff --git a/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc b/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc
index 0253607b313..93e9addc4d7 100644
--- a/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc
+++ b/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc
@@ -775,10 +775,13 @@ It is highly recommended to leave CSRF protection enabled.
The CsrfTokenRepository to use.
The default is `HttpSessionCsrfTokenRepository`.
-[[nsa-csrf-request-attribute-name]]
-* **request-attribute-name**
-Optional attribute that specifies the request attribute name to set the `CsrfToken` on.
-The default is `CsrfToken.parameterName`.
+[[nsa-csrf-request-attribute-handler-ref]]
+* **request-attribute-handler-ref**
+The optional `CsrfTokenRequestAttributeHandler` to use. The default is `CsrfTokenRequestProcessor`.
+
+[[nsa-csrf-request-resolver-ref]]
+* **request-resolver-ref**
+The optional `CsrfTokenRequestResolver` to use. The default is `CsrfTokenRequestProcessor`.
[[nsa-csrf-request-matcher-ref]]
* **request-matcher-ref**
diff --git a/web/src/main/java/org/springframework/security/web/csrf/CsrfAuthenticationStrategy.java b/web/src/main/java/org/springframework/security/web/csrf/CsrfAuthenticationStrategy.java
index f6a19a266d7..b61a20d7d5e 100644
--- a/web/src/main/java/org/springframework/security/web/csrf/CsrfAuthenticationStrategy.java
+++ b/web/src/main/java/org/springframework/security/web/csrf/CsrfAuthenticationStrategy.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2022 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.
@@ -41,6 +41,8 @@ public final class CsrfAuthenticationStrategy implements SessionAuthenticationSt
private final CsrfTokenRepository csrfTokenRepository;
+ private CsrfTokenRequestAttributeHandler requestAttributeHandler = new CsrfTokenRequestProcessor();
+
/**
* Creates a new instance
* @param csrfTokenRepository the {@link CsrfTokenRepository} to use
@@ -50,6 +52,16 @@ public CsrfAuthenticationStrategy(CsrfTokenRepository csrfTokenRepository) {
this.csrfTokenRepository = csrfTokenRepository;
}
+ /**
+ * Specify a {@link CsrfTokenRequestAttributeHandler} to use for making the
+ * {@code CsrfToken} available as a request attribute.
+ * @param requestAttributeHandler the {@link CsrfTokenRequestAttributeHandler} to use
+ */
+ public void setRequestAttributeHandler(CsrfTokenRequestAttributeHandler requestAttributeHandler) {
+ Assert.notNull(requestAttributeHandler, "requestAttributeHandler cannot be null");
+ this.requestAttributeHandler = requestAttributeHandler;
+ }
+
@Override
public void onAuthentication(Authentication authentication, HttpServletRequest request,
HttpServletResponse response) throws SessionAuthenticationException {
@@ -58,8 +70,7 @@ public void onAuthentication(Authentication authentication, HttpServletRequest r
this.csrfTokenRepository.saveToken(null, request, response);
CsrfToken newToken = this.csrfTokenRepository.generateToken(request);
this.csrfTokenRepository.saveToken(newToken, request, response);
- request.setAttribute(CsrfToken.class.getName(), newToken);
- request.setAttribute(newToken.getParameterName(), newToken);
+ this.requestAttributeHandler.handle(request, response, () -> newToken);
this.logger.debug("Replaced CSRF Token");
}
}
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 1c452ecf457..0033bf571ec 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2022 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.
@@ -58,6 +58,7 @@
*
*
* @author Rob Winch
+ * @author Steve Riesenberg
* @since 3.2
*/
public final class CsrfFilter extends OncePerRequestFilter {
@@ -87,11 +88,16 @@ public final class CsrfFilter extends OncePerRequestFilter {
private AccessDeniedHandler accessDeniedHandler = new AccessDeniedHandlerImpl();
- private String csrfRequestAttributeName;
+ private CsrfTokenRequestAttributeHandler requestAttributeHandler;
+
+ private CsrfTokenRequestResolver requestResolver;
public CsrfFilter(CsrfTokenRepository csrfTokenRepository) {
Assert.notNull(csrfTokenRepository, "csrfTokenRepository cannot be null");
this.tokenRepository = csrfTokenRepository;
+ CsrfTokenRequestProcessor csrfTokenRequestProcessor = new CsrfTokenRequestProcessor();
+ this.requestAttributeHandler = csrfTokenRequestProcessor;
+ this.requestResolver = csrfTokenRequestProcessor;
}
@Override
@@ -109,10 +115,8 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
csrfToken = this.tokenRepository.generateToken(request);
this.tokenRepository.saveToken(csrfToken, request, response);
}
- request.setAttribute(CsrfToken.class.getName(), csrfToken);
- String csrfAttrName = (this.csrfRequestAttributeName != null) ? this.csrfRequestAttributeName
- : csrfToken.getParameterName();
- request.setAttribute(csrfAttrName, csrfToken);
+ final CsrfToken finalCsrfToken = csrfToken;
+ this.requestAttributeHandler.handle(request, response, () -> finalCsrfToken);
if (!this.requireCsrfProtectionMatcher.matches(request)) {
if (this.logger.isTraceEnabled()) {
this.logger.trace("Did not protect against CSRF since request did not match "
@@ -121,10 +125,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
filterChain.doFilter(request, response);
return;
}
- String actualToken = request.getHeader(csrfToken.getHeaderName());
- if (actualToken == null) {
- actualToken = request.getParameter(csrfToken.getParameterName());
- }
+ String actualToken = this.requestResolver.resolveCsrfTokenValue(request, csrfToken);
if (!equalsConstantTime(csrfToken.getToken(), actualToken)) {
this.logger.debug(
LogMessage.of(() -> "Invalid CSRF token found for " + UrlUtils.buildFullRequestUrl(request)));
@@ -172,15 +173,33 @@ public void setAccessDeniedHandler(AccessDeniedHandler accessDeniedHandler) {
}
/**
- * The {@link CsrfToken} is available as a request attribute named
- * {@code CsrfToken.class.getName()}. By default, an additional request attribute that
- * is the same as {@link CsrfToken#getParameterName()} is set. This attribute allows
- * overriding the additional attribute.
- * @param csrfRequestAttributeName the name of an additional request attribute with
- * the value of the CsrfToken. Default is {@link CsrfToken#getParameterName()}
+ * Specifies a {@link CsrfTokenRequestAttributeHandler} that is used to make the
+ * {@link CsrfToken} available as a request attribute.
+ *
+ *
+ * The default is {@link CsrfTokenRequestProcessor}.
+ *
+ * @param requestAttributeHandler the {@link CsrfTokenRequestAttributeHandler} to use
+ * @since 5.8
+ */
+ public void setRequestAttributeHandler(CsrfTokenRequestAttributeHandler requestAttributeHandler) {
+ Assert.notNull(requestAttributeHandler, "requestAttributeHandler cannot be null");
+ this.requestAttributeHandler = requestAttributeHandler;
+ }
+
+ /**
+ * Specifies a {@link CsrfTokenRequestResolver} that is used to resolve the token
+ * value from the request.
+ *
+ *
+ * The default is {@link CsrfTokenRequestProcessor}.
+ *
+ * @param requestResolver the {@link CsrfTokenRequestResolver} to use
+ * @since 5.8
*/
- public void setCsrfRequestAttributeName(String csrfRequestAttributeName) {
- this.csrfRequestAttributeName = csrfRequestAttributeName;
+ public void setRequestResolver(CsrfTokenRequestResolver requestResolver) {
+ Assert.notNull(requestResolver, "requestResolver cannot be null");
+ this.requestResolver = requestResolver;
}
/**
diff --git a/web/src/main/java/org/springframework/security/web/csrf/CsrfTokenRequestAttributeHandler.java b/web/src/main/java/org/springframework/security/web/csrf/CsrfTokenRequestAttributeHandler.java
new file mode 100644
index 00000000000..a22f3144d22
--- /dev/null
+++ b/web/src/main/java/org/springframework/security/web/csrf/CsrfTokenRequestAttributeHandler.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2002-2022 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
+ *
+ * https://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 java.util.function.Supplier;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * A callback interface that is used to make the {@link CsrfToken} created by the
+ * {@link CsrfTokenRepository} available as a request attribute. Implementations of this
+ * interface may choose to perform additional tasks or customize how the token is made
+ * available to the application through request attributes.
+ *
+ * @author Steve Riesenberg
+ * @since 5.8
+ * @see CsrfTokenRequestProcessor
+ */
+@FunctionalInterface
+public interface CsrfTokenRequestAttributeHandler {
+
+ /**
+ * Handles a request using a {@link CsrfToken}.
+ * @param request the {@code HttpServletRequest} being handled
+ * @param response the {@code HttpServletResponse} being handled
+ * @param csrfToken the {@link CsrfToken} created by the {@link CsrfTokenRepository}
+ */
+ void handle(HttpServletRequest request, HttpServletResponse response, Supplier csrfToken);
+
+}
diff --git a/web/src/main/java/org/springframework/security/web/csrf/CsrfTokenRequestProcessor.java b/web/src/main/java/org/springframework/security/web/csrf/CsrfTokenRequestProcessor.java
new file mode 100644
index 00000000000..47807a1dc1e
--- /dev/null
+++ b/web/src/main/java/org/springframework/security/web/csrf/CsrfTokenRequestProcessor.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2002-2022 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
+ *
+ * https://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 java.util.function.Supplier;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.util.Assert;
+
+/**
+ * An implementation of the {@link CsrfTokenRequestAttributeHandler} and
+ * {@link CsrfTokenRequestResolver} interfaces that is capable of making the
+ * {@link CsrfToken} available as a request attribute and resolving the token value as
+ * either a header or parameter value of the request.
+ *
+ * @author Steve Riesenberg
+ * @since 5.8
+ */
+public class CsrfTokenRequestProcessor implements CsrfTokenRequestAttributeHandler, CsrfTokenRequestResolver {
+
+ private String csrfRequestAttributeName;
+
+ /**
+ * The {@link CsrfToken} is available as a request attribute named
+ * {@code CsrfToken.class.getName()}. By default, an additional request attribute that
+ * is the same as {@link CsrfToken#getParameterName()} is set. This attribute allows
+ * overriding the additional attribute.
+ * @param csrfRequestAttributeName the name of an additional request attribute with
+ * the value of the CsrfToken. Default is {@link CsrfToken#getParameterName()}
+ */
+ public final void setCsrfRequestAttributeName(String csrfRequestAttributeName) {
+ this.csrfRequestAttributeName = csrfRequestAttributeName;
+ }
+
+ @Override
+ public void handle(HttpServletRequest request, HttpServletResponse response, Supplier csrfToken) {
+ Assert.notNull(request, "request cannot be null");
+ Assert.notNull(response, "response cannot be null");
+ Assert.notNull(csrfToken, "csrfToken supplier cannot be null");
+ CsrfToken actualCsrfToken = csrfToken.get();
+ Assert.notNull(actualCsrfToken, "csrfToken cannot be null");
+ request.setAttribute(CsrfToken.class.getName(), actualCsrfToken);
+ String csrfAttrName = (this.csrfRequestAttributeName != null) ? this.csrfRequestAttributeName
+ : actualCsrfToken.getParameterName();
+ request.setAttribute(csrfAttrName, actualCsrfToken);
+ }
+
+ @Override
+ public String resolveCsrfTokenValue(HttpServletRequest request, CsrfToken csrfToken) {
+ Assert.notNull(request, "request cannot be null");
+ Assert.notNull(csrfToken, "csrfToken cannot be null");
+ String actualToken = request.getHeader(csrfToken.getHeaderName());
+ if (actualToken == null) {
+ actualToken = request.getParameter(csrfToken.getParameterName());
+ }
+ return actualToken;
+ }
+
+}
diff --git a/web/src/main/java/org/springframework/security/web/csrf/CsrfTokenRequestResolver.java b/web/src/main/java/org/springframework/security/web/csrf/CsrfTokenRequestResolver.java
new file mode 100644
index 00000000000..f3d820d2ca7
--- /dev/null
+++ b/web/src/main/java/org/springframework/security/web/csrf/CsrfTokenRequestResolver.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2002-2022 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
+ *
+ * https://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 javax.servlet.http.HttpServletRequest;
+
+/**
+ * Implementations of this interface are capable of resolving the token value of a
+ * {@link CsrfToken} from the provided {@code HttpServletRequest}. Used by the
+ * {@link CsrfFilter}.
+ *
+ * @author Steve Riesenberg
+ * @since 5.8
+ * @see CsrfTokenRequestProcessor
+ */
+@FunctionalInterface
+public interface CsrfTokenRequestResolver {
+
+ /**
+ * Returns the token value resolved from the provided {@code HttpServletRequest} and
+ * {@link CsrfToken} or {@code null} if not available.
+ * @param request the {@code HttpServletRequest} being processed
+ * @param csrfToken the {@link CsrfToken} created by the {@link CsrfTokenRepository}
+ * @return the token value resolved from the request
+ */
+ String resolveCsrfTokenValue(HttpServletRequest request, CsrfToken csrfToken);
+
+}
diff --git a/web/src/test/java/org/springframework/security/web/csrf/CsrfAuthenticationStrategyTests.java b/web/src/test/java/org/springframework/security/web/csrf/CsrfAuthenticationStrategyTests.java
index a949e3a3958..9872522aa0e 100644
--- a/web/src/test/java/org/springframework/security/web/csrf/CsrfAuthenticationStrategyTests.java
+++ b/web/src/test/java/org/springframework/security/web/csrf/CsrfAuthenticationStrategyTests.java
@@ -34,8 +34,10 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
/**
* @author Rob Winch
@@ -72,6 +74,25 @@ public void constructorNullCsrfTokenRepository() {
assertThatIllegalArgumentException().isThrownBy(() -> new CsrfAuthenticationStrategy(null));
}
+ @Test
+ public void setRequestAttributeHandlerWhenNullThenIllegalStateException() {
+ assertThatIllegalArgumentException().isThrownBy(() -> this.strategy.setRequestAttributeHandler(null))
+ .withMessage("requestAttributeHandler cannot be null");
+ }
+
+ @Test
+ public void onAuthenticationWhenCustomRequestAttributeHandlerThenUsed() {
+ given(this.csrfTokenRepository.loadToken(this.request)).willReturn(this.existingToken);
+ given(this.csrfTokenRepository.generateToken(this.request)).willReturn(this.generatedToken);
+
+ CsrfTokenRequestAttributeHandler requestAttributeHandler = mock(CsrfTokenRequestAttributeHandler.class);
+ this.strategy.setRequestAttributeHandler(requestAttributeHandler);
+ this.strategy.onAuthentication(new TestingAuthenticationToken("user", "password", "ROLE_USER"), this.request,
+ this.response);
+ verify(requestAttributeHandler).handle(eq(this.request), eq(this.response), any());
+ verifyNoMoreInteractions(requestAttributeHandler);
+ }
+
@Test
public void logoutRemovesCsrfTokenAndSavesNew() {
given(this.csrfTokenRepository.loadToken(this.request)).willReturn(this.existingToken);
diff --git a/web/src/test/java/org/springframework/security/web/csrf/CsrfFilterTests.java b/web/src/test/java/org/springframework/security/web/csrf/CsrfFilterTests.java
index f107df43b0e..443375c35a8 100644
--- a/web/src/test/java/org/springframework/security/web/csrf/CsrfFilterTests.java
+++ b/web/src/test/java/org/springframework/security/web/csrf/CsrfFilterTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2022 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.
@@ -335,6 +335,30 @@ public void doFilterWhenTokenIsNullThenNoNullPointer() throws Exception {
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
}
+ @Test
+ public void doFilterWhenRequestAttributeHandlerThenUsed() throws Exception {
+ given(this.requestMatcher.matches(this.request)).willReturn(true);
+ given(this.tokenRepository.loadToken(this.request)).willReturn(this.token);
+ CsrfTokenRequestAttributeHandler requestAttributeHandler = mock(CsrfTokenRequestAttributeHandler.class);
+ this.filter.setRequestAttributeHandler(requestAttributeHandler);
+ this.request.setParameter(this.token.getParameterName(), this.token.getToken());
+ this.filter.doFilter(this.request, this.response, this.filterChain);
+ verify(requestAttributeHandler).handle(eq(this.request), eq(this.response), any());
+ verify(this.filterChain).doFilter(this.request, this.response);
+ }
+
+ @Test
+ public void doFilterWhenRequestResolverThenUsed() throws Exception {
+ given(this.requestMatcher.matches(this.request)).willReturn(true);
+ given(this.tokenRepository.loadToken(this.request)).willReturn(this.token);
+ CsrfTokenRequestResolver requestResolver = mock(CsrfTokenRequestResolver.class);
+ given(requestResolver.resolveCsrfTokenValue(this.request, this.token)).willReturn(this.token.getToken());
+ this.filter.setRequestResolver(requestResolver);
+ this.filter.doFilter(this.request, this.response, this.filterChain);
+ verify(requestResolver).resolveCsrfTokenValue(this.request, this.token);
+ verify(this.filterChain).doFilter(this.request, this.response);
+ }
+
@Test
public void setRequireCsrfProtectionMatcherNull() {
assertThatIllegalArgumentException().isThrownBy(() -> this.filter.setRequireCsrfProtectionMatcher(null));
@@ -351,7 +375,9 @@ public void doFilterWhenCsrfRequestAttributeNameThenNoCsrfTokenMethodInvokedOnGe
throws ServletException, IOException {
CsrfFilter filter = createCsrfFilter(this.tokenRepository);
String csrfAttrName = "_csrf";
- filter.setCsrfRequestAttributeName(csrfAttrName);
+ CsrfTokenRequestProcessor csrfTokenRequestProcessor = new CsrfTokenRequestProcessor();
+ csrfTokenRequestProcessor.setCsrfRequestAttributeName(csrfAttrName);
+ filter.setRequestAttributeHandler(csrfTokenRequestProcessor);
CsrfToken expectedCsrfToken = mock(CsrfToken.class);
given(this.tokenRepository.loadToken(this.request)).willReturn(expectedCsrfToken);
diff --git a/web/src/test/java/org/springframework/security/web/csrf/CsrfTokenRequestProcessorTests.java b/web/src/test/java/org/springframework/security/web/csrf/CsrfTokenRequestProcessorTests.java
new file mode 100644
index 00000000000..ac50ec3aaa8
--- /dev/null
+++ b/web/src/test/java/org/springframework/security/web/csrf/CsrfTokenRequestProcessorTests.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2002-2022 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
+ *
+ * https://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.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpServletResponse;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+
+/**
+ * Tests for {@link CsrfTokenRequestProcessor}.
+ *
+ * @author Steve Riesenberg
+ * @since 5.8
+ */
+public class CsrfTokenRequestProcessorTests {
+
+ private MockHttpServletRequest request;
+
+ private MockHttpServletResponse response;
+
+ private CsrfToken token;
+
+ private CsrfTokenRequestProcessor processor;
+
+ @BeforeEach
+ public void setup() {
+ this.request = new MockHttpServletRequest();
+ this.response = new MockHttpServletResponse();
+ this.token = new DefaultCsrfToken("headerName", "paramName", "csrfTokenValue");
+ this.processor = new CsrfTokenRequestProcessor();
+ }
+
+ @Test
+ public void handleWhenRequestIsNullThenThrowsIllegalArgumentException() {
+ assertThatIllegalArgumentException()
+ .isThrownBy(() -> this.processor.handle(null, this.response, () -> this.token))
+ .withMessage("request cannot be null");
+ }
+
+ @Test
+ public void handleWhenResponseIsNullThenThrowsIllegalArgumentException() {
+ assertThatIllegalArgumentException()
+ .isThrownBy(() -> this.processor.handle(this.request, null, () -> this.token))
+ .withMessage("response cannot be null");
+ }
+
+ @Test
+ public void handleWhenCsrfTokenSupplierIsNullThenThrowsIllegalArgumentException() {
+ assertThatIllegalArgumentException().isThrownBy(() -> this.processor.handle(this.request, this.response, null))
+ .withMessage("csrfToken supplier cannot be null");
+ }
+
+ @Test
+ public void handleWhenCsrfTokenIsNullThenThrowsIllegalArgumentException() {
+ assertThatIllegalArgumentException()
+ .isThrownBy(() -> this.processor.handle(this.request, this.response, () -> null))
+ .withMessage("csrfToken cannot be null");
+ }
+
+ @Test
+ public void handleWhenCsrfRequestAttributeSetThenUsed() {
+ this.processor.setCsrfRequestAttributeName("_csrf");
+ this.processor.handle(this.request, this.response, () -> this.token);
+ assertThat(this.request.getAttribute(CsrfToken.class.getName())).isEqualTo(this.token);
+ assertThat(this.request.getAttribute("_csrf")).isEqualTo(this.token);
+ }
+
+ @Test
+ public void handleWhenValidParametersThenRequestAttributesSet() {
+ this.processor.handle(this.request, this.response, () -> this.token);
+ assertThat(this.request.getAttribute(CsrfToken.class.getName())).isEqualTo(this.token);
+ assertThat(this.request.getAttribute(this.token.getParameterName())).isEqualTo(this.token);
+ }
+
+ @Test
+ public void resolveCsrfTokenValueWhenRequestIsNullThenThrowsIllegalArgumentException() {
+ assertThatIllegalArgumentException().isThrownBy(() -> this.processor.resolveCsrfTokenValue(null, this.token))
+ .withMessage("request cannot be null");
+ }
+
+ @Test
+ public void resolveCsrfTokenValueWhenCsrfTokenIsNullThenThrowsIllegalArgumentException() {
+ assertThatIllegalArgumentException().isThrownBy(() -> this.processor.resolveCsrfTokenValue(this.request, null))
+ .withMessage("csrfToken cannot be null");
+ }
+
+ @Test
+ public void resolveCsrfTokenValueWhenTokenNotSetThenReturnsNull() {
+ String tokenValue = this.processor.resolveCsrfTokenValue(this.request, this.token);
+ assertThat(tokenValue).isNull();
+ }
+
+ @Test
+ public void resolveCsrfTokenValueWhenParameterSetThenReturnsTokenValue() {
+ this.request.setParameter(this.token.getParameterName(), this.token.getToken());
+ String tokenValue = this.processor.resolveCsrfTokenValue(this.request, this.token);
+ assertThat(tokenValue).isEqualTo(this.token.getToken());
+ }
+
+ @Test
+ public void resolveCsrfTokenValueWhenHeaderSetThenReturnsTokenValue() {
+ this.request.addHeader(this.token.getHeaderName(), this.token.getToken());
+ String tokenValue = this.processor.resolveCsrfTokenValue(this.request, this.token);
+ assertThat(tokenValue).isEqualTo(this.token.getToken());
+ }
+
+ @Test
+ public void resolveCsrfTokenValueWhenHeaderAndParameterSetThenHeaderIsPreferred() {
+ this.request.addHeader(this.token.getHeaderName(), "header");
+ this.request.setParameter(this.token.getParameterName(), "parameter");
+ String tokenValue = this.processor.resolveCsrfTokenValue(this.request, this.token);
+ assertThat(tokenValue).isEqualTo("header");
+ }
+
+}