diff --git a/config/src/main/kotlin/org/springframework/security/config/annotation/web/CsrfDsl.kt b/config/src/main/kotlin/org/springframework/security/config/annotation/web/CsrfDsl.kt index dc53cd88c20..32995fddbaf 100644 --- a/config/src/main/kotlin/org/springframework/security/config/annotation/web/CsrfDsl.kt +++ b/config/src/main/kotlin/org/springframework/security/config/annotation/web/CsrfDsl.kt @@ -47,6 +47,7 @@ class CsrfDsl { private var ignoringRequestMatchers: Array? = null private var ignoringRequestMatchersPatterns: Array? = null private var disabled = false + private var spaMode = false /** * Allows specifying [HttpServletRequest]s that should not use CSRF Protection @@ -76,6 +77,17 @@ class CsrfDsl { disabled = true } + /** + * Sensible CSRF defaults when used in combination with a single page application. + * Creates a cookie-based token repository and a custom request handler to resolve the + * actual token value instead of the encoded token. + * + * @since 7.1 + */ + fun spa() { + spaMode = true + } + internal fun get(): (CsrfConfigurer) -> Unit { return { csrf -> csrfTokenRepository?.also { csrf.csrfTokenRepository(csrfTokenRepository) } @@ -84,6 +96,9 @@ class CsrfDsl { csrfTokenRequestHandler?.also { csrf.csrfTokenRequestHandler(csrfTokenRequestHandler) } ignoringRequestMatchers?.also { csrf.ignoringRequestMatchers(*ignoringRequestMatchers!!) } ignoringRequestMatchersPatterns?.also { csrf.ignoringRequestMatchers(*ignoringRequestMatchersPatterns!!) } + if (spaMode) { + csrf.spa() + } if (disabled) { csrf.disable() } diff --git a/config/src/test/kotlin/org/springframework/security/config/annotation/web/CsrfDslTests.kt b/config/src/test/kotlin/org/springframework/security/config/annotation/web/CsrfDslTests.kt index 286fbb9a86f..c7d1eb5812e 100644 --- a/config/src/test/kotlin/org/springframework/security/config/annotation/web/CsrfDslTests.kt +++ b/config/src/test/kotlin/org/springframework/security/config/annotation/web/CsrfDslTests.kt @@ -343,4 +343,40 @@ class CsrfDslTests { return http.build() } } + + @Test + fun `POST when CSRF for SPA enabled and no CSRF token then forbidden`() { + this.spring.register(CsrfSPAConfig::class.java).autowire() + + this.mockMvc.post("/test1") + .andExpect { + status { isForbidden() } + } + } + + @Test + fun `POST when CSRF for SPA enabled and CSRF token then status OK`() { + this.spring.register(CsrfSPAConfig::class.java, BasicController::class.java).autowire() + + this.mockMvc.post("/test1") { + with(csrf()) + }.andExpect { + status { isOk() } + } + + } + + @Configuration + @EnableWebSecurity + open class CsrfSPAConfig { + @Bean + open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { + http { + csrf { + spa() + } + } + return http.build() + } + } }