1649 lines
56 KiB
Plaintext
1649 lines
56 KiB
Plaintext
[[servlet-csrf]]
|
||
= Cross Site Request Forgery (CSRF)
|
||
:figures: servlet/exploits
|
||
|
||
In an application where end users can xref:servlet/authentication/index.adoc[log in], it is important to consider how to protect against xref:features/exploits/csrf.adoc#csrf[Cross Site Request Forgery (CSRF)].
|
||
|
||
Spring Security protects against CSRF attacks by default for xref:features/exploits/csrf.adoc#csrf-protection-read-only[unsafe HTTP methods], such as a POST request, so no additional code is necessary.
|
||
You can specify the default configuration explicitly using the following:
|
||
|
||
[[csrf-configuration]]
|
||
.Configure CSRF Protection
|
||
[tabs]
|
||
======
|
||
Java::
|
||
+
|
||
[source,java,role="primary"]
|
||
----
|
||
@Configuration
|
||
@EnableWebSecurity
|
||
public class SecurityConfig {
|
||
|
||
@Bean
|
||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||
http
|
||
// ...
|
||
.csrf(Customizer.withDefaults());
|
||
return http.build();
|
||
}
|
||
}
|
||
----
|
||
|
||
Kotlin::
|
||
+
|
||
[source,kotlin,role="secondary"]
|
||
----
|
||
import org.springframework.security.config.annotation.web.invoke
|
||
|
||
@Configuration
|
||
@EnableWebSecurity
|
||
class SecurityConfig {
|
||
|
||
@Bean
|
||
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
|
||
http {
|
||
// ...
|
||
csrf { }
|
||
}
|
||
return http.build()
|
||
}
|
||
}
|
||
----
|
||
|
||
XML::
|
||
+
|
||
[source,xml,role="secondary"]
|
||
----
|
||
<http>
|
||
<!-- ... -->
|
||
<csrf/>
|
||
</http>
|
||
----
|
||
======
|
||
|
||
To learn more about CSRF protection for your application, consider the following use cases:
|
||
|
||
* I want to <<csrf-components,understand CSRF protection's components>>
|
||
* I need to <<migrating-to-spring-security-6,migrate an application from Spring Security 5 to 6>>
|
||
* I want to <<csrf-token-repository-cookie,store the `CsrfToken` in a cookie>> instead of <<csrf-token-repository-httpsession,the session>>
|
||
* I want to <<csrf-token-repository-custom,store the `CsrfToken` in a custom location>>
|
||
* I want to <<deferred-csrf-token-opt-out,opt-out of deferred tokens>>
|
||
* I want to <<csrf-token-request-handler-opt-out-of-breach,opt-out of BREACH protection>>
|
||
* I need guidance integrating <<csrf-integration-form,Thymeleaf, JSPs or another view technology>> with the backend
|
||
* I need guidance integrating <<csrf-integration-javascript,Angular or another JavaScript framework>> with the backend
|
||
* I need guidance integrating <<csrf-integration-mobile,a mobile application or another client>> with the backend
|
||
* I need guidance on <<csrf-access-denied-handler,handling errors>>
|
||
* I want to <<csrf-testing,test CSRF protection>>
|
||
* I need guidance on <<disable-csrf,disabling CSRF protection>>
|
||
|
||
[[csrf-components]]
|
||
== Understanding CSRF Protection's Components
|
||
|
||
CSRF protection is provided by several components that are composed within the {security-api-url}org/springframework/security/web/csrf/CsrfFilter.html[`CsrfFilter`]:
|
||
|
||
.`CsrfFilter` Components
|
||
image::{figures}/csrf.png[]
|
||
|
||
CSRF protection is divided into two parts:
|
||
|
||
1. Make the {security-api-url}org/springframework/security/web/csrf/CsrfToken.html[`CsrfToken`] available to the application by delegating to the <<csrf-token-request-handler,`CsrfTokenRequestHandler`>>.
|
||
2. Determine if the request requires CSRF protection, load and validate the token, and <<csrf-access-denied-handler,handle `AccessDeniedException`>>.
|
||
|
||
.`CsrfFilter` Processing
|
||
image::{figures}/csrf-processing.png[]
|
||
|
||
* image:{icondir}/number_1.png[] First, the {security-api-url}org/springframework/security/web/csrf/DeferredCsrfToken.html[`DeferredCsrfToken`] is loaded, which holds a reference to the <<csrf-token-repository,`CsrfTokenRepository`>> so that the persisted `CsrfToken` can be loaded later (in image:{icondir}/number_4.png[]).
|
||
* image:{icondir}/number_2.png[] Second, a `Supplier<CsrfToken>` (created from `DeferredCsrfToken`) is given to the <<csrf-token-request-handler,`CsrfTokenRequestHandler`>>, which is responsible for populating a request attribute to make the `CsrfToken` available to the rest of the application.
|
||
* image:{icondir}/number_3.png[] Next, the main CSRF protection processing begins and checks if the current request requires CSRF protection. If not required, the filter chain is continued and processing ends.
|
||
* image:{icondir}/number_4.png[] If CSRF protection is required, the persisted `CsrfToken` is finally loaded from the `DeferredCsrfToken`.
|
||
* image:{icondir}/number_5.png[] Continuing, the actual CSRF token provided by the client (if any) is resolved using the <<csrf-token-request-handler,`CsrfTokenRequestHandler`>>.
|
||
* image:{icondir}/number_6.png[] The actual CSRF token is compared against the persisted `CsrfToken`. If valid, the filter chain is continued and processing ends.
|
||
* image:{icondir}/number_7.png[] If the actual CSRF token is invalid (or missing), an `AccessDeniedException` is passed to the <<csrf-access-denied-handler,`AccessDeniedHandler`>> and processing ends.
|
||
|
||
[[migrating-to-spring-security-6]]
|
||
== Migrating to Spring Security 6
|
||
|
||
When migrating from Spring Security 5 to 6, there are a few changes that may impact your application.
|
||
The following is an overview of the aspects of CSRF protection that have changed in Spring Security 6:
|
||
|
||
* Loading of the `CsrfToken` is now <<deferred-csrf-token,deferred by default>> to improve performance by no longer requiring the session to be loaded on every request.
|
||
* The `CsrfToken` now includes <<csrf-token-request-handler-breach,randomness on every request by default>> to protect the CSRF token from a https://en.wikipedia.org/wiki/BREACH[BREACH] attack.
|
||
|
||
[TIP]
|
||
====
|
||
The changes in Spring Security 6 require additional configuration for single-page applications, and as such you may find the <<csrf-integration-javascript-spa>> section particularly useful.
|
||
====
|
||
|
||
See the https://docs.spring.io/spring-security/reference/5.8/migration/servlet/exploits.html[Exploit Protection] section of the https://docs.spring.io/spring-security/reference/5.8/migration/index.html[Migration] chapter for more information on migrating a Spring Security 5 application.
|
||
|
||
[[csrf-token-repository]]
|
||
== Persisting the `CsrfToken`
|
||
|
||
The `CsrfToken` is persisted using a `CsrfTokenRepository`.
|
||
|
||
By default, the <<csrf-token-repository-httpsession,`HttpSessionCsrfTokenRepository`>> is used for storing tokens in a session.
|
||
Spring Security also provides the <<csrf-token-repository-cookie,`CookieCsrfTokenRepository`>> for storing tokens in a cookie.
|
||
You can also specify <<csrf-token-repository-custom,your own implementation>> to store tokens wherever you like.
|
||
|
||
[[csrf-token-repository-httpsession]]
|
||
=== Using the `HttpSessionCsrfTokenRepository`
|
||
|
||
By default, Spring Security stores the expected CSRF token in the `HttpSession` by using {security-api-url}org/springframework/security/web/csrf/HttpSessionCsrfTokenRepository.html[`HttpSessionCsrfTokenRepository`], so no additional code is necessary.
|
||
|
||
The `HttpSessionCsrfTokenRepository` reads the token from a session (whether in-memory, cache, or database). If you need to access the session attribute directly, please first configure the session attribute name using `HttpSessionCsrfTokenRepository#setSessionAttributeName`.
|
||
|
||
You can specify the default configuration explicitly using the following configuration:
|
||
|
||
[[csrf-token-repository-httpsession-configuration]]
|
||
.Configure `HttpSessionCsrfTokenRepository`
|
||
[tabs]
|
||
======
|
||
Java::
|
||
+
|
||
[source,java,role="primary"]
|
||
----
|
||
@Configuration
|
||
@EnableWebSecurity
|
||
public class SecurityConfig {
|
||
|
||
@Bean
|
||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||
http
|
||
// ...
|
||
.csrf((csrf) -> csrf
|
||
.csrfTokenRepository(new HttpSessionCsrfTokenRepository())
|
||
);
|
||
return http.build();
|
||
}
|
||
}
|
||
----
|
||
|
||
Kotlin::
|
||
+
|
||
[source,kotlin,role="secondary"]
|
||
----
|
||
import org.springframework.security.config.annotation.web.invoke
|
||
|
||
@Configuration
|
||
@EnableWebSecurity
|
||
class SecurityConfig {
|
||
|
||
@Bean
|
||
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
|
||
http {
|
||
// ...
|
||
csrf {
|
||
csrfTokenRepository = HttpSessionCsrfTokenRepository()
|
||
}
|
||
}
|
||
return http.build()
|
||
}
|
||
}
|
||
----
|
||
|
||
XML::
|
||
+
|
||
[source,xml,role="secondary"]
|
||
----
|
||
<http>
|
||
<!-- ... -->
|
||
<csrf token-repository-ref="tokenRepository"/>
|
||
</http>
|
||
<b:bean id="tokenRepository"
|
||
class="org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository"/>
|
||
----
|
||
======
|
||
|
||
[[csrf-token-repository-cookie]]
|
||
=== Using the `CookieCsrfTokenRepository`
|
||
|
||
You can persist the `CsrfToken` in a cookie to <<csrf-integration-javascript,support a JavaScript-based application>> using the {security-api-url}org/springframework/security/web/csrf/CookieCsrfTokenRepository.html[`CookieCsrfTokenRepository`].
|
||
|
||
The `CookieCsrfTokenRepository` writes to a cookie named `XSRF-TOKEN` and reads it from an HTTP request header named `X-XSRF-TOKEN` or the request parameter `_csrf` by default.
|
||
These defaults come from Angular and its predecessor https://docs.angularjs.org/api/ng/service/$http#cross-site-request-forgery-xsrf-protection[AngularJS].
|
||
|
||
[TIP]
|
||
====
|
||
See the https://angular.io/guide/http-security-xsrf-protection[Cross-Site Request Forgery (XSRF) protection] guide and the https://angular.io/api/common/http/HttpClientXsrfModule[HttpClientXsrfModule] for more recent information on this topic.
|
||
====
|
||
|
||
You can configure the `CookieCsrfTokenRepository` using the following configuration:
|
||
|
||
[[csrf-token-repository-cookie-configuration]]
|
||
.Configure `CookieCsrfTokenRepository`
|
||
[tabs]
|
||
======
|
||
Java::
|
||
+
|
||
[source,java,role="primary"]
|
||
----
|
||
@Configuration
|
||
@EnableWebSecurity
|
||
public class SecurityConfig {
|
||
|
||
@Bean
|
||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||
http
|
||
// ...
|
||
.csrf((csrf) -> csrf
|
||
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
|
||
);
|
||
return http.build();
|
||
}
|
||
}
|
||
----
|
||
|
||
Kotlin::
|
||
+
|
||
[source,kotlin,role="secondary"]
|
||
----
|
||
import org.springframework.security.config.annotation.web.invoke
|
||
|
||
@Configuration
|
||
@EnableWebSecurity
|
||
class SecurityConfig {
|
||
|
||
@Bean
|
||
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
|
||
http {
|
||
// ...
|
||
csrf {
|
||
csrfTokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse()
|
||
}
|
||
}
|
||
return http.build()
|
||
}
|
||
}
|
||
----
|
||
|
||
XML::
|
||
+
|
||
[source,xml,role="secondary"]
|
||
----
|
||
<http>
|
||
<!-- ... -->
|
||
<csrf token-repository-ref="tokenRepository"/>
|
||
</http>
|
||
<b:bean id="tokenRepository"
|
||
class="org.springframework.security.web.csrf.CookieCsrfTokenRepository"
|
||
p:cookieHttpOnly="false"/>
|
||
----
|
||
======
|
||
|
||
[NOTE]
|
||
====
|
||
The example explicitly sets `HttpOnly` to `false`.
|
||
This is necessary to let JavaScript frameworks (such as Angular) read it.
|
||
If you do not need the ability to read the cookie with JavaScript directly, we _recommend_ omitting `HttpOnly` (by using `new CookieCsrfTokenRepository()` instead) to improve security.
|
||
====
|
||
|
||
[[csrf-token-repository-custom]]
|
||
=== Customizing the `CsrfTokenRepository`
|
||
|
||
There can be cases where you want to implement a custom {security-api-url}org/springframework/security/web/csrf/CsrfTokenRepository.html[`CsrfTokenRepository`].
|
||
|
||
Once you've implemented the `CsrfTokenRepository` interface, you can configure Spring Security to use it with the following configuration:
|
||
|
||
[[csrf-token-repository-custom-configuration]]
|
||
.Configure Custom `CsrfTokenRepository`
|
||
[tabs]
|
||
======
|
||
Java::
|
||
+
|
||
[source,java,role="primary"]
|
||
----
|
||
@Configuration
|
||
@EnableWebSecurity
|
||
public class SecurityConfig {
|
||
|
||
@Bean
|
||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||
http
|
||
// ...
|
||
.csrf((csrf) -> csrf
|
||
.csrfTokenRepository(new CustomCsrfTokenRepository())
|
||
);
|
||
return http.build();
|
||
}
|
||
}
|
||
----
|
||
|
||
Kotlin::
|
||
+
|
||
[source,kotlin,role="secondary"]
|
||
----
|
||
import org.springframework.security.config.annotation.web.invoke
|
||
|
||
@Configuration
|
||
@EnableWebSecurity
|
||
class SecurityConfig {
|
||
|
||
@Bean
|
||
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
|
||
http {
|
||
// ...
|
||
csrf {
|
||
csrfTokenRepository = CustomCsrfTokenRepository()
|
||
}
|
||
}
|
||
return http.build()
|
||
}
|
||
}
|
||
----
|
||
|
||
XML::
|
||
+
|
||
[source,xml,role="secondary"]
|
||
----
|
||
<http>
|
||
<!-- ... -->
|
||
<csrf token-repository-ref="tokenRepository"/>
|
||
</http>
|
||
<b:bean id="tokenRepository"
|
||
class="example.CustomCsrfTokenRepository"/>
|
||
----
|
||
======
|
||
|
||
[[csrf-token-request-handler]]
|
||
== Handling the `CsrfToken`
|
||
|
||
The `CsrfToken` is made available to an application using a `CsrfTokenRequestHandler`.
|
||
This component is also responsible for resolving the `CsrfToken` from HTTP headers or request parameters.
|
||
|
||
By default, the <<csrf-token-request-handler-breach,`XorCsrfTokenRequestAttributeHandler`>> is used for providing https://en.wikipedia.org/wiki/BREACH[BREACH] protection of the `CsrfToken`.
|
||
Spring Security also provides the <<csrf-token-request-handler-plain,`CsrfTokenRequestAttributeHandler`>> for opting out of BREACH protection.
|
||
You can also specify <<csrf-token-request-handler-custom,your own implementation>> to customize the strategy for handling and resolving tokens.
|
||
|
||
[[csrf-token-request-handler-breach]]
|
||
=== Using the `XorCsrfTokenRequestAttributeHandler` (BREACH)
|
||
|
||
The `XorCsrfTokenRequestAttributeHandler` makes the `CsrfToken` available as an `HttpServletRequest` attribute called `_csrf`, and additionally provides protection for https://en.wikipedia.org/wiki/BREACH[BREACH].
|
||
|
||
[NOTE]
|
||
====
|
||
The `CsrfToken` is also made available as a request attribute using the name `CsrfToken.class.getName()`.
|
||
This name is not configurable, but the name `_csrf` can be changed using `XorCsrfTokenRequestAttributeHandler#setCsrfRequestAttributeName`.
|
||
====
|
||
|
||
This implementation also resolves the token value from the request as either a request header (one of <<csrf-token-repository-httpsession,`X-CSRF-TOKEN`>> or <<csrf-token-repository-cookie,`X-XSRF-TOKEN`>> by default) or a request parameter (`_csrf` by default).
|
||
|
||
[NOTE]
|
||
====
|
||
BREACH protection is provided by encoding randomness into the CSRF token value to ensure the returned `CsrfToken` changes on every request.
|
||
When the token is later resolved as a header value or request parameter, it is decoded to obtain the raw token which is then compared to the <<csrf-token-repository,persisted `CsrfToken`>>.
|
||
====
|
||
|
||
Spring Security protects the CSRF token from a BREACH attack by default, so no additional code is necessary.
|
||
You can specify the default configuration explicitly using the following configuration:
|
||
|
||
[[csrf-token-request-handler-breach-configuration]]
|
||
.Configure BREACH protection
|
||
[tabs]
|
||
======
|
||
Java::
|
||
+
|
||
[source,java,role="primary"]
|
||
----
|
||
@Configuration
|
||
@EnableWebSecurity
|
||
public class SecurityConfig {
|
||
|
||
@Bean
|
||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||
http
|
||
// ...
|
||
.csrf((csrf) -> csrf
|
||
.csrfTokenRequestHandler(new XorCsrfTokenRequestAttributeHandler())
|
||
);
|
||
return http.build();
|
||
}
|
||
}
|
||
----
|
||
|
||
Kotlin::
|
||
+
|
||
[source,kotlin,role="secondary"]
|
||
----
|
||
import org.springframework.security.config.annotation.web.invoke
|
||
|
||
@Configuration
|
||
@EnableWebSecurity
|
||
class SecurityConfig {
|
||
|
||
@Bean
|
||
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
|
||
http {
|
||
// ...
|
||
csrf {
|
||
csrfTokenRequestHandler = XorCsrfTokenRequestAttributeHandler()
|
||
}
|
||
}
|
||
return http.build()
|
||
}
|
||
}
|
||
----
|
||
|
||
XML::
|
||
+
|
||
[source,xml,role="secondary"]
|
||
----
|
||
<http>
|
||
<!-- ... -->
|
||
<csrf request-handler-ref="requestHandler"/>
|
||
</http>
|
||
<b:bean id="requestHandler"
|
||
class="org.springframework.security.web.csrf.XorCsrfTokenRequestAttributeHandler"/>
|
||
----
|
||
======
|
||
|
||
[[csrf-token-request-handler-plain]]
|
||
=== Using the `CsrfTokenRequestAttributeHandler`
|
||
|
||
The `CsrfTokenRequestAttributeHandler` makes the `CsrfToken` available as an `HttpServletRequest` attribute called `_csrf`.
|
||
|
||
[NOTE]
|
||
====
|
||
The `CsrfToken` is also made available as a request attribute using the name `CsrfToken.class.getName()`.
|
||
This name is not configurable, but the name `_csrf` can be changed using `CsrfTokenRequestAttributeHandler#setCsrfRequestAttributeName`.
|
||
====
|
||
|
||
This implementation also resolves the token value from the request as either a request header (one of <<csrf-token-repository-httpsession,`X-CSRF-TOKEN`>> or <<csrf-token-repository-cookie,`X-XSRF-TOKEN`>> by default) or a request parameter (`_csrf` by default).
|
||
|
||
[[csrf-token-request-handler-opt-out-of-breach]]
|
||
The primary use of `CsrfTokenRequestAttributeHandler` is to opt-out of BREACH protection of the `CsrfToken`, which can be configured using the following configuration:
|
||
|
||
.Opt-out of BREACH protection
|
||
[tabs]
|
||
======
|
||
Java::
|
||
+
|
||
[source,java,role="primary"]
|
||
----
|
||
@Configuration
|
||
@EnableWebSecurity
|
||
public class SecurityConfig {
|
||
|
||
@Bean
|
||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||
http
|
||
// ...
|
||
.csrf((csrf) -> csrf
|
||
.csrfTokenRequestHandler(new CsrfTokenRequestAttributeHandler())
|
||
);
|
||
return http.build();
|
||
}
|
||
}
|
||
----
|
||
|
||
Kotlin::
|
||
+
|
||
[source,kotlin,role="secondary"]
|
||
----
|
||
import org.springframework.security.config.annotation.web.invoke
|
||
|
||
@Configuration
|
||
@EnableWebSecurity
|
||
class SecurityConfig {
|
||
|
||
@Bean
|
||
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
|
||
http {
|
||
// ...
|
||
csrf {
|
||
csrfTokenRequestHandler = CsrfTokenRequestAttributeHandler()
|
||
}
|
||
}
|
||
return http.build()
|
||
}
|
||
}
|
||
----
|
||
|
||
XML::
|
||
+
|
||
[source,xml,role="secondary"]
|
||
----
|
||
<http>
|
||
<!-- ... -->
|
||
<csrf request-handler-ref="requestHandler"/>
|
||
</http>
|
||
<b:bean id="requestHandler"
|
||
class="org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler"/>
|
||
----
|
||
======
|
||
|
||
[[csrf-token-request-handler-custom]]
|
||
=== Customizing the `CsrfTokenRequestHandler`
|
||
|
||
You can implement the `CsrfTokenRequestHandler` interface to customize the strategy for handling and resolving tokens.
|
||
|
||
[TIP]
|
||
====
|
||
The `CsrfTokenRequestHandler` interface is a `@FunctionalInterface` that can be implemented using a lambda expression to customize request handling.
|
||
You will need to implement the full interface to customize how tokens are resolved from the request.
|
||
See <<csrf-integration-javascript-spa-configuration>> for an example that uses delegation to implement a custom strategy for handling and resolving tokens.
|
||
====
|
||
|
||
Once you've implemented the `CsrfTokenRequestHandler` interface, you can configure Spring Security to use it with the following configuration:
|
||
|
||
[[csrf-token-request-handler-custom-configuration]]
|
||
.Configure Custom `CsrfTokenRequestHandler`
|
||
[tabs]
|
||
======
|
||
Java::
|
||
+
|
||
[source,java,role="primary"]
|
||
----
|
||
@Configuration
|
||
@EnableWebSecurity
|
||
public class SecurityConfig {
|
||
|
||
@Bean
|
||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||
http
|
||
// ...
|
||
.csrf((csrf) -> csrf
|
||
.csrfTokenRequestHandler(new CustomCsrfTokenRequestHandler())
|
||
);
|
||
return http.build();
|
||
}
|
||
}
|
||
----
|
||
|
||
Kotlin::
|
||
+
|
||
[source,kotlin,role="secondary"]
|
||
----
|
||
import org.springframework.security.config.annotation.web.invoke
|
||
|
||
@Configuration
|
||
@EnableWebSecurity
|
||
class SecurityConfig {
|
||
|
||
@Bean
|
||
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
|
||
http {
|
||
// ...
|
||
csrf {
|
||
csrfTokenRequestHandler = CustomCsrfTokenRequestHandler()
|
||
}
|
||
}
|
||
return http.build()
|
||
}
|
||
}
|
||
----
|
||
|
||
XML::
|
||
+
|
||
[source,xml,role="secondary"]
|
||
----
|
||
<http>
|
||
<!-- ... -->
|
||
<csrf request-handler-ref="requestHandler"/>
|
||
</http>
|
||
<b:bean id="requestHandler"
|
||
class="example.CustomCsrfTokenRequestHandler"/>
|
||
----
|
||
======
|
||
|
||
[[deferred-csrf-token]]
|
||
== Deferred Loading of the `CsrfToken`
|
||
|
||
By default, Spring Security defers loading of the `CsrfToken` until it is needed.
|
||
|
||
[NOTE]
|
||
====
|
||
The `CsrfToken` is needed whenever a request is made with an xref:features/exploits/csrf.adoc#csrf-protection-read-only[unsafe HTTP method], such as a POST.
|
||
Additionally, it is needed by any request that renders the token to the response, such as a web page with a `<form>` tag that includes a hidden `<input>` for the CSRF token.
|
||
====
|
||
|
||
Because Spring Security also stores the `CsrfToken` in the `HttpSession` by default, deferred CSRF tokens can improve performance by not requiring the session to be loaded on every request.
|
||
|
||
[[deferred-csrf-token-opt-out]]
|
||
In the event that you want to opt-out of deferred tokens and cause the `CsrfToken` to be loaded on every request, you can do so with the following configuration:
|
||
|
||
[[deferred-csrf-token-opt-out-configuration]]
|
||
.Opt-out of Deferred CSRF Tokens
|
||
[tabs]
|
||
======
|
||
Java::
|
||
+
|
||
[source,java,role="primary"]
|
||
----
|
||
@Configuration
|
||
@EnableWebSecurity
|
||
public class SecurityConfig {
|
||
|
||
@Bean
|
||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||
XorCsrfTokenRequestAttributeHandler requestHandler = new XorCsrfTokenRequestAttributeHandler();
|
||
// set the name of the attribute the CsrfToken will be populated on
|
||
requestHandler.setCsrfRequestAttributeName(null);
|
||
http
|
||
// ...
|
||
.csrf((csrf) -> csrf
|
||
.csrfTokenRequestHandler(requestHandler)
|
||
);
|
||
return http.build();
|
||
}
|
||
}
|
||
----
|
||
|
||
Kotlin::
|
||
+
|
||
[source,kotlin,role="secondary"]
|
||
----
|
||
import org.springframework.security.config.annotation.web.invoke
|
||
|
||
@Configuration
|
||
@EnableWebSecurity
|
||
class SecurityConfig {
|
||
|
||
@Bean
|
||
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
|
||
val requestHandler = XorCsrfTokenRequestAttributeHandler()
|
||
// set the name of the attribute the CsrfToken will be populated on
|
||
requestHandler.setCsrfRequestAttributeName(null)
|
||
http {
|
||
// ...
|
||
csrf {
|
||
csrfTokenRequestHandler = requestHandler
|
||
}
|
||
}
|
||
return http.build()
|
||
}
|
||
}
|
||
----
|
||
|
||
XML::
|
||
+
|
||
[source,xml,role="secondary"]
|
||
----
|
||
<http>
|
||
<!-- ... -->
|
||
<csrf request-handler-ref="requestHandler"/>
|
||
</http>
|
||
<b:bean id="requestHandler"
|
||
class="org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler">
|
||
<b:property name="csrfRequestAttributeName">
|
||
<b:null/>
|
||
</b:property>
|
||
</b:bean>
|
||
----
|
||
======
|
||
|
||
[NOTE]
|
||
====
|
||
By setting the `csrfRequestAttributeName` to `null`, the `CsrfToken` must first be loaded to determine what attribute name to use.
|
||
This causes the `CsrfToken` to be loaded on every request.
|
||
====
|
||
|
||
|
||
[[csrf-integration]]
|
||
== Integrating with CSRF Protection
|
||
|
||
For the xref:features/exploits/csrf.adoc#csrf-protection-stp[synchronizer token pattern] to protect against CSRF attacks, we must include the actual CSRF token in the HTTP request.
|
||
This must be included in a part of the request (a form parameter, an HTTP header, or other part) that is not automatically included in the HTTP request by the browser.
|
||
|
||
The following sections describe the various ways a frontend or client application can integrate with a CSRF-protected backend application:
|
||
|
||
* <<csrf-integration-form>>
|
||
* <<csrf-integration-javascript>>
|
||
* <<csrf-integration-mobile>>
|
||
|
||
[[csrf-integration-form]]
|
||
=== HTML Forms
|
||
|
||
To submit an HTML form, the CSRF token must be included in the form as a hidden input.
|
||
For example, the rendered HTML might look like:
|
||
|
||
.CSRF Token in HTML Form
|
||
[source,html]
|
||
----
|
||
<input type="hidden"
|
||
name="_csrf"
|
||
value="4bfd1575-3ad1-4d21-96c7-4ef2d9f86721"/>
|
||
----
|
||
|
||
The following view technologies automatically include the actual CSRF token in a form that has an unsafe HTTP method, such as a POST:
|
||
|
||
* https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-view-jsp-formtaglib[Spring’s form tag library]
|
||
* https://www.thymeleaf.org/doc/tutorials/2.1/thymeleafspring.html#integration-with-requestdatavalueprocessor[Thymeleaf]
|
||
* Any other view technology that integrates with {spring-framework-api-url}org/springframework/web/servlet/support/RequestDataValueProcessor.html[`RequestDataValueProcessor`] (via {security-api-url}org/springframework/security/web/servlet/support/csrf/CsrfRequestDataValueProcessor.html[`CsrfRequestDataValueProcessor`])
|
||
* You can also include the token yourself via the xref:servlet/integrations/jsp-taglibs.adoc#taglibs-csrfinput[csrfInput] tag
|
||
|
||
If these options are not available, you can take advantage of the fact that the `CsrfToken` is exposed as an <<csrf-token-request-handler,`HttpServletRequest` attribute named `_csrf`>>.
|
||
The following example does this with a JSP:
|
||
|
||
.CSRF Token in HTML Form with Request Attribute
|
||
[source,xml]
|
||
----
|
||
<c:url var="logoutUrl" value="/logout"/>
|
||
<form action="${logoutUrl}"
|
||
method="post">
|
||
<input type="submit"
|
||
value="Log out" />
|
||
<input type="hidden"
|
||
name="${_csrf.parameterName}"
|
||
value="${_csrf.token}"/>
|
||
</form>
|
||
----
|
||
|
||
[[csrf-integration-javascript]]
|
||
=== JavaScript Applications
|
||
|
||
JavaScript applications typically use JSON instead of HTML.
|
||
If you use JSON, you can submit the CSRF token within an HTTP request header instead of a request parameter.
|
||
|
||
In order to obtain the CSRF token, you can configure Spring Security to store the expected CSRF token <<csrf-token-repository-cookie,in a cookie>>.
|
||
By storing the expected token in a cookie, JavaScript frameworks such as https://angular.io/api/common/http/HttpClientXsrfModule[Angular] can automatically include the actual CSRF token as an HTTP request header.
|
||
|
||
[TIP]
|
||
====
|
||
There are special considerations for BREACH protection and deferred tokens when integrating a single-page application (SPA) with Spring Security's CSRF protection.
|
||
A full configuration example is provided in the <<csrf-integration-javascript-spa,next section>>.
|
||
====
|
||
|
||
You can read about different types of JavaScript applications in the following sections:
|
||
|
||
* <<csrf-integration-javascript-spa>>
|
||
* <<csrf-integration-javascript-mpa>>
|
||
* <<csrf-integration-javascript-other>>
|
||
|
||
[[csrf-integration-javascript-spa]]
|
||
==== Single-Page Applications
|
||
|
||
There are special considerations for integrating a single-page application (SPA) with Spring Security's CSRF protection.
|
||
|
||
Recall that Spring Security provides <<csrf-token-request-handler-breach,BREACH protection of the `CsrfToken`>> by default.
|
||
When storing the expected CSRF token <<csrf-token-repository-cookie,in a cookie>>, JavaScript applications will only have access to the plain token value and _will not_ have access to the encoded value.
|
||
A <<csrf-token-request-handler-custom,customized request handler>> for resolving the actual token value will need to be provided.
|
||
|
||
In addition, the cookie storing the CSRF token will be cleared upon authentication success and logout success.
|
||
Spring Security defers loading a new CSRF token by default, and additional work is required to return a fresh cookie.
|
||
|
||
[NOTE]
|
||
====
|
||
Refreshing the token after authentication success and logout success is required because the {security-api-url}org/springframework/security/web/csrf/CsrfAuthenticationStrategy.html[`CsrfAuthenticationStrategy`] and {security-api-url}org/springframework/security/web/csrf/CsrfLogoutHandler.html[`CsrfLogoutHandler`] will clear the previous token.
|
||
The client application will not be able to perform an unsafe HTTP request, such as a POST, without obtaining a fresh token.
|
||
====
|
||
|
||
In order to easily integrate a single-page application with Spring Security, the following configuration can be used:
|
||
|
||
[[csrf-integration-javascript-spa-configuration]]
|
||
.Configure CSRF for Single-Page Application
|
||
[tabs]
|
||
======
|
||
Java::
|
||
+
|
||
[source,java,role="primary"]
|
||
----
|
||
@Configuration
|
||
@EnableWebSecurity
|
||
public class SecurityConfig {
|
||
|
||
@Bean
|
||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||
http
|
||
// ...
|
||
.csrf((csrf) -> csrf
|
||
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) // <1>
|
||
.csrfTokenRequestHandler(new SpaCsrfTokenRequestHandler()) // <2>
|
||
)
|
||
.addFilterAfter(new CsrfCookieFilter(), BasicAuthenticationFilter.class); // <3>
|
||
return http.build();
|
||
}
|
||
}
|
||
|
||
final class SpaCsrfTokenRequestHandler extends CsrfTokenRequestAttributeHandler {
|
||
private final CsrfTokenRequestHandler delegate = new XorCsrfTokenRequestAttributeHandler();
|
||
|
||
@Override
|
||
public void handle(HttpServletRequest request, HttpServletResponse response, Supplier<CsrfToken> csrfToken) {
|
||
/*
|
||
* Always use XorCsrfTokenRequestAttributeHandler to provide BREACH protection of
|
||
* the CsrfToken when it is rendered in the response body.
|
||
*/
|
||
this.delegate.handle(request, response, csrfToken);
|
||
}
|
||
|
||
@Override
|
||
public String resolveCsrfTokenValue(HttpServletRequest request, CsrfToken csrfToken) {
|
||
/*
|
||
* If the request contains a request header, use CsrfTokenRequestAttributeHandler
|
||
* to resolve the CsrfToken. This applies when a single-page application includes
|
||
* the header value automatically, which was obtained via a cookie containing the
|
||
* raw CsrfToken.
|
||
*/
|
||
if (StringUtils.hasText(request.getHeader(csrfToken.getHeaderName()))) {
|
||
return super.resolveCsrfTokenValue(request, csrfToken);
|
||
}
|
||
/*
|
||
* In all other cases (e.g. if the request contains a request parameter), use
|
||
* XorCsrfTokenRequestAttributeHandler to resolve the CsrfToken. This applies
|
||
* when a server-side rendered form includes the _csrf request parameter as a
|
||
* hidden input.
|
||
*/
|
||
return this.delegate.resolveCsrfTokenValue(request, csrfToken);
|
||
}
|
||
}
|
||
|
||
final class CsrfCookieFilter extends OncePerRequestFilter {
|
||
|
||
@Override
|
||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||
throws ServletException, IOException {
|
||
CsrfToken csrfToken = (CsrfToken) request.getAttribute("_csrf");
|
||
// Render the token value to a cookie by causing the deferred token to be loaded
|
||
csrfToken.getToken();
|
||
|
||
filterChain.doFilter(request, response);
|
||
}
|
||
}
|
||
----
|
||
|
||
Kotlin::
|
||
+
|
||
[source,kotlin,role="secondary"]
|
||
----
|
||
import org.springframework.security.config.annotation.web.invoke
|
||
|
||
@Configuration
|
||
@EnableWebSecurity
|
||
class SecurityConfig {
|
||
|
||
@Bean
|
||
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
|
||
http {
|
||
// ...
|
||
csrf {
|
||
csrfTokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse() // <1>
|
||
csrfTokenRequestHandler = SpaCsrfTokenRequestHandler() // <2>
|
||
}
|
||
}
|
||
http.addFilterAfter(CsrfCookieFilter(), BasicAuthenticationFilter::class.java) // <3>
|
||
return http.build()
|
||
}
|
||
}
|
||
|
||
class SpaCsrfTokenRequestHandler : CsrfTokenRequestAttributeHandler() {
|
||
private val delegate: CsrfTokenRequestHandler = XorCsrfTokenRequestAttributeHandler()
|
||
|
||
override fun handle(request: HttpServletRequest, response: HttpServletResponse, csrfToken: Supplier<CsrfToken>) {
|
||
/*
|
||
* Always use XorCsrfTokenRequestAttributeHandler to provide BREACH protection of
|
||
* the CsrfToken when it is rendered in the response body.
|
||
*/
|
||
delegate.handle(request, response, csrfToken)
|
||
}
|
||
|
||
override fun resolveCsrfTokenValue(request: HttpServletRequest, csrfToken: CsrfToken): String? {
|
||
/*
|
||
* If the request contains a request header, use CsrfTokenRequestAttributeHandler
|
||
* to resolve the CsrfToken. This applies when a single-page application includes
|
||
* the header value automatically, which was obtained via a cookie containing the
|
||
* raw CsrfToken.
|
||
*/
|
||
return if (StringUtils.hasText(request.getHeader(csrfToken.headerName))) {
|
||
super.resolveCsrfTokenValue(request, csrfToken)
|
||
} else {
|
||
/*
|
||
* In all other cases (e.g. if the request contains a request parameter), use
|
||
* XorCsrfTokenRequestAttributeHandler to resolve the CsrfToken. This applies
|
||
* when a server-side rendered form includes the _csrf request parameter as a
|
||
* hidden input.
|
||
*/
|
||
delegate.resolveCsrfTokenValue(request, csrfToken)
|
||
}
|
||
}
|
||
}
|
||
|
||
class CsrfCookieFilter : OncePerRequestFilter() {
|
||
|
||
@Throws(ServletException::class, IOException::class)
|
||
override fun doFilterInternal(request: HttpServletRequest, response: HttpServletResponse, filterChain: FilterChain) {
|
||
val csrfToken = request.getAttribute("_csrf") as CsrfToken
|
||
// Render the token value to a cookie by causing the deferred token to be loaded
|
||
csrfToken.token
|
||
filterChain.doFilter(request, response)
|
||
}
|
||
}
|
||
----
|
||
|
||
XML::
|
||
+
|
||
[source,xml,role="secondary"]
|
||
----
|
||
<http>
|
||
<!-- ... -->
|
||
<csrf
|
||
token-repository-ref="tokenRepository" <1>
|
||
request-handler-ref="requestHandler"/> <2>
|
||
<custom-filter ref="csrfCookieFilter" after="BASIC_AUTH_FILTER"/> <3>
|
||
</http>
|
||
<b:bean id="tokenRepository"
|
||
class="org.springframework.security.web.csrf.CookieCsrfTokenRepository"
|
||
p:cookieHttpOnly="false"/>
|
||
<b:bean id="requestHandler"
|
||
class="example.SpaCsrfTokenRequestHandler"/>
|
||
<b:bean id="csrfCookieFilter"
|
||
class="example.CsrfCookieFilter"/>
|
||
----
|
||
======
|
||
|
||
<1> Configure `CookieCsrfTokenRepository` with `HttpOnly` set to `false` so the cookie can be read by the JavaScript application.
|
||
<2> Configure a custom `CsrfTokenRequestHandler` that resolves the CSRF token based on whether it is an HTTP request header (`X-XSRF-TOKEN`) or request parameter (`_csrf`).
|
||
<3> Configure a custom `Filter` to load the `CsrfToken` on every request, which will return a new cookie if needed.
|
||
|
||
[[csrf-integration-javascript-mpa]]
|
||
==== Multi-Page Applications
|
||
|
||
For multi-page applications where JavaScript is loaded on each page, an alternative to exposing the CSRF token <<csrf-token-repository-cookie,in a cookie>> is to include the CSRF token within your `meta` tags.
|
||
The HTML might look something like this:
|
||
|
||
.CSRF Token in HTML Meta Tag
|
||
[source,html]
|
||
----
|
||
<html>
|
||
<head>
|
||
<meta name="_csrf" content="4bfd1575-3ad1-4d21-96c7-4ef2d9f86721"/>
|
||
<meta name="_csrf_header" content="X-CSRF-TOKEN"/>
|
||
<!-- ... -->
|
||
</head>
|
||
<!-- ... -->
|
||
</html>
|
||
----
|
||
|
||
In order to include the CSRF token in the request, you can take advantage of the fact that the `CsrfToken` is exposed as an <<csrf-token-request-handler,`HttpServletRequest` attribute named `_csrf`>>.
|
||
The following example does this with a JSP:
|
||
|
||
.CSRF Token in HTML Meta Tag with Request Attribute
|
||
[source,html]
|
||
----
|
||
<html>
|
||
<head>
|
||
<meta name="_csrf" content="${_csrf.token}"/>
|
||
<!-- default header name is X-CSRF-TOKEN -->
|
||
<meta name="_csrf_header" content="${_csrf.headerName}"/>
|
||
<!-- ... -->
|
||
</head>
|
||
<!-- ... -->
|
||
</html>
|
||
----
|
||
|
||
Once the meta tags contain the CSRF token, the JavaScript code can read the meta tags and include the CSRF token as a header.
|
||
If you use jQuery, you can do this with the following code:
|
||
|
||
.Include CSRF Token in AJAX Request
|
||
[source,javascript]
|
||
----
|
||
$(function () {
|
||
var token = $("meta[name='_csrf']").attr("content");
|
||
var header = $("meta[name='_csrf_header']").attr("content");
|
||
$(document).ajaxSend(function(e, xhr, options) {
|
||
xhr.setRequestHeader(header, token);
|
||
});
|
||
});
|
||
----
|
||
|
||
[[csrf-integration-javascript-other]]
|
||
==== Other JavaScript Applications
|
||
|
||
Another option for JavaScript applications is to include the CSRF token in an HTTP response header.
|
||
|
||
One way to achieve this is through the use of a `@ControllerAdvice` with the xref:servlet/integrations/mvc.adoc#mvc-csrf-resolver[`CsrfTokenArgumentResolver`].
|
||
The following is an example of `@ControllerAdvice` that applies to all controller endpoints in the application:
|
||
|
||
[[controller-advice]]
|
||
.CSRF Token in HTTP Response Header
|
||
[tabs]
|
||
======
|
||
Java::
|
||
+
|
||
[source,java,role="primary"]
|
||
----
|
||
@ControllerAdvice
|
||
public class CsrfControllerAdvice {
|
||
|
||
@ModelAttribute
|
||
public void getCsrfToken(HttpServletResponse response, CsrfToken csrfToken) {
|
||
response.setHeader(csrfToken.getHeaderName(), csrfToken.getToken());
|
||
}
|
||
|
||
}
|
||
----
|
||
|
||
Kotlin::
|
||
+
|
||
[source,kotlin,role="secondary"]
|
||
----
|
||
@ControllerAdvice
|
||
class CsrfControllerAdvice {
|
||
|
||
@ModelAttribute
|
||
fun getCsrfToken(response: HttpServletResponse, csrfToken: CsrfToken) {
|
||
response.setHeader(csrfToken.headerName, csrfToken.token)
|
||
}
|
||
|
||
}
|
||
----
|
||
======
|
||
|
||
[NOTE]
|
||
====
|
||
Because this `@ControllerAdvice` applies to all endpoints in the application, it will cause the CSRF token to be loaded on every request, which can negate the benefits of <<deferred-csrf-token,deferred tokens>> when using the <<csrf-token-repository-httpsession,`HttpSessionCsrfTokenRepository`>>.
|
||
However, this is not usually an issue when using the <<csrf-token-repository-cookie,`CookieCsrfTokenRepository`>>.
|
||
====
|
||
|
||
[NOTE]
|
||
====
|
||
It is important to remember that controller endpoints and controller advice are called _after_ the Spring Security filter chain.
|
||
This means that this `@ControllerAdvice` will only be applied if the request passes through the filter chain to your application.
|
||
See the configuration for <<csrf-integration-javascript-spa-configuration,single-page applications>> for an example of adding a filter to the filter chain for earlier access to the `HttpServletResponse`.
|
||
====
|
||
|
||
The CSRF token will now be available in a response header (<<csrf-token-repository-httpsession,`X-CSRF-TOKEN`>> or <<csrf-token-repository-cookie,`X-XSRF-TOKEN`>> by default) for any custom endpoints the controller advice applies to.
|
||
Any request to the backend can be used to obtain the token from the response, and a subsequent request can include the token in a request header with the same name.
|
||
|
||
[[csrf-integration-mobile]]
|
||
=== Mobile Applications
|
||
|
||
Like <<csrf-integration-javascript,JavaScript applications>>, mobile applications typically use JSON instead of HTML.
|
||
A backend application that _does not_ serve browser traffic may choose to <<disable-csrf,disable CSRF>>.
|
||
In that case, no additional work is required.
|
||
|
||
However, a backend application that also serves browser traffic and therefore _still requires_ CSRF protection may continue to store the `CsrfToken` <<csrf-token-repository-httpsession,in the session>> instead of <<csrf-token-repository-cookie,in a cookie>>.
|
||
|
||
In this case, a typical pattern for integrating with the backend is to expose a `/csrf` endpoint to allow the frontend (mobile or browser client) to request a CSRF token on demand.
|
||
The benefit of using this pattern is that the CSRF token <<deferred-csrf-token,can continue to be deferred>> and only needs to be loaded from the session when a request requires CSRF protection.
|
||
The use of a custom endpoint also means the client application can request that a new token be generated on demand (if necessary) by issuing an explicit request.
|
||
|
||
[TIP]
|
||
====
|
||
This pattern can be used for any type of application that requires CSRF protection, not just mobile applications.
|
||
While this approach isn't typically required in those cases, it is another option for integrating with a CSRF-protected backend.
|
||
====
|
||
|
||
The following is an example of the `/csrf` endpoint that makes use of the xref:servlet/integrations/mvc.adoc#mvc-csrf-resolver[`CsrfTokenArgumentResolver`]:
|
||
|
||
[[csrf-endpoint]]
|
||
.The `/csrf` endpoint
|
||
[tabs]
|
||
======
|
||
Java::
|
||
+
|
||
[source,java,role="primary"]
|
||
----
|
||
@RestController
|
||
public class CsrfController {
|
||
|
||
@GetMapping("/csrf")
|
||
public CsrfToken csrf(CsrfToken csrfToken) {
|
||
return csrfToken;
|
||
}
|
||
|
||
}
|
||
----
|
||
|
||
Kotlin::
|
||
+
|
||
[source,kotlin,role="secondary"]
|
||
----
|
||
@RestController
|
||
class CsrfController {
|
||
|
||
@GetMapping("/csrf")
|
||
fun csrf(csrfToken: CsrfToken): CsrfToken {
|
||
return csrfToken
|
||
}
|
||
|
||
}
|
||
----
|
||
======
|
||
|
||
[NOTE]
|
||
====
|
||
You may consider adding `.requestMatchers("/csrf").permitAll()` if the endpoint above is required prior to authenticating with the server.
|
||
====
|
||
|
||
This endpoint should be called to obtain a CSRF token when the application is launched or initialized (e.g. at load time), and also after authentication success and logout success.
|
||
|
||
[NOTE]
|
||
====
|
||
Refreshing the token after authentication success and logout success is required because the {security-api-url}org/springframework/security/web/csrf/CsrfAuthenticationStrategy.html[`CsrfAuthenticationStrategy`] and {security-api-url}org/springframework/security/web/csrf/CsrfLogoutHandler.html[`CsrfLogoutHandler`] will clear the previous token.
|
||
The client application will not be able to perform an unsafe HTTP request, such as a POST, without obtaining a fresh token.
|
||
====
|
||
|
||
Once you've obtained the CSRF token, you will need to include it as an HTTP request header (one of <<csrf-token-repository-httpsession,`X-CSRF-TOKEN`>> or <<csrf-token-repository-cookie,`X-XSRF-TOKEN`>> by default) yourself.
|
||
|
||
[[csrf-access-denied-handler]]
|
||
== Handle `AccessDeniedException`
|
||
|
||
To handle an `AccessDeniedException` such as `InvalidCsrfTokenException`, you can configure Spring Security to handle these exceptions in any way you like.
|
||
For example, you can configure a custom access denied page using the following configuration:
|
||
|
||
[[csrf-access-denied-handler-configuration]]
|
||
.Configure `AccessDeniedHandler`
|
||
[tabs]
|
||
======
|
||
Java::
|
||
+
|
||
[source,java,role="primary"]
|
||
----
|
||
@Configuration
|
||
@EnableWebSecurity
|
||
public class SecurityConfig {
|
||
|
||
@Bean
|
||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||
http
|
||
// ...
|
||
.exceptionHandling((exceptionHandling) -> exceptionHandling
|
||
.accessDeniedPage("/access-denied")
|
||
);
|
||
return http.build();
|
||
}
|
||
}
|
||
----
|
||
|
||
Kotlin::
|
||
+
|
||
[source,kotlin,role="secondary"]
|
||
----
|
||
import org.springframework.security.config.annotation.web.invoke
|
||
|
||
@Configuration
|
||
@EnableWebSecurity
|
||
class SecurityConfig {
|
||
|
||
@Bean
|
||
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
|
||
http {
|
||
// ...
|
||
exceptionHandling {
|
||
accessDeniedPage = "/access-denied"
|
||
}
|
||
}
|
||
return http.build()
|
||
}
|
||
}
|
||
----
|
||
|
||
XML::
|
||
+
|
||
[source,xml,role="secondary"]
|
||
----
|
||
<http>
|
||
<!-- ... -->
|
||
<access-denied-handler error-page="/access-denied"/>
|
||
</http>
|
||
----
|
||
======
|
||
|
||
[[csrf-testing]]
|
||
== CSRF Testing
|
||
|
||
You can use Spring Security's xref:servlet/test/mockmvc/setup.adoc[testing support] and xref:servlet/test/mockmvc/csrf.adoc[`CsrfRequestPostProcessor`] to test CSRF protection, like this:
|
||
|
||
[[csrf-testing-example]]
|
||
.Test CSRF Protection
|
||
[tabs]
|
||
======
|
||
Java::
|
||
+
|
||
[source,java,role="primary"]
|
||
----
|
||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*;
|
||
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.*;
|
||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
|
||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
|
||
|
||
@ExtendWith(SpringExtension.class)
|
||
@ContextConfiguration(classes = SecurityConfig.class)
|
||
@WebAppConfiguration
|
||
public class CsrfTests {
|
||
|
||
private MockMvc mockMvc;
|
||
|
||
@BeforeEach
|
||
public void setUp(WebApplicationContext applicationContext) {
|
||
this.mockMvc = MockMvcBuilders.webAppContextSetup(applicationContext)
|
||
.apply(springSecurity())
|
||
.build();
|
||
}
|
||
|
||
@Test
|
||
public void loginWhenValidCsrfTokenThenSuccess() throws Exception {
|
||
this.mockMvc.perform(post("/login").with(csrf())
|
||
.accept(MediaType.TEXT_HTML)
|
||
.param("username", "user")
|
||
.param("password", "password"))
|
||
.andExpect(status().is3xxRedirection())
|
||
.andExpect(header().string(HttpHeaders.LOCATION, "/"));
|
||
}
|
||
|
||
@Test
|
||
public void loginWhenInvalidCsrfTokenThenForbidden() throws Exception {
|
||
this.mockMvc.perform(post("/login").with(csrf().useInvalidToken())
|
||
.accept(MediaType.TEXT_HTML)
|
||
.param("username", "user")
|
||
.param("password", "password"))
|
||
.andExpect(status().isForbidden());
|
||
}
|
||
|
||
@Test
|
||
public void loginWhenMissingCsrfTokenThenForbidden() throws Exception {
|
||
this.mockMvc.perform(post("/login")
|
||
.accept(MediaType.TEXT_HTML)
|
||
.param("username", "user")
|
||
.param("password", "password"))
|
||
.andExpect(status().isForbidden());
|
||
}
|
||
|
||
@Test
|
||
@WithMockUser
|
||
public void logoutWhenValidCsrfTokenThenSuccess() throws Exception {
|
||
this.mockMvc.perform(post("/logout").with(csrf())
|
||
.accept(MediaType.TEXT_HTML))
|
||
.andExpect(status().is3xxRedirection())
|
||
.andExpect(header().string(HttpHeaders.LOCATION, "/login?logout"));
|
||
}
|
||
}
|
||
----
|
||
|
||
Kotlin::
|
||
+
|
||
[source,kotlin,role="secondary"]
|
||
----
|
||
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*
|
||
import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.*
|
||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*
|
||
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.*
|
||
|
||
@ExtendWith(SpringExtension::class)
|
||
@ContextConfiguration(classes = [SecurityConfig::class])
|
||
@WebAppConfiguration
|
||
class CsrfTests {
|
||
private lateinit var mockMvc: MockMvc
|
||
|
||
@BeforeEach
|
||
fun setUp(applicationContext: WebApplicationContext) {
|
||
mockMvc = MockMvcBuilders.webAppContextSetup(applicationContext)
|
||
.apply<DefaultMockMvcBuilder>(springSecurity())
|
||
.build()
|
||
}
|
||
|
||
@Test
|
||
fun loginWhenValidCsrfTokenThenSuccess() {
|
||
mockMvc.perform(post("/login").with(csrf())
|
||
.accept(MediaType.TEXT_HTML)
|
||
.param("username", "user")
|
||
.param("password", "password"))
|
||
.andExpect(status().is3xxRedirection)
|
||
.andExpect(header().string(HttpHeaders.LOCATION, "/"))
|
||
}
|
||
|
||
@Test
|
||
fun loginWhenInvalidCsrfTokenThenForbidden() {
|
||
mockMvc.perform(post("/login").with(csrf().useInvalidToken())
|
||
.accept(MediaType.TEXT_HTML)
|
||
.param("username", "user")
|
||
.param("password", "password"))
|
||
.andExpect(status().isForbidden)
|
||
}
|
||
|
||
@Test
|
||
fun loginWhenMissingCsrfTokenThenForbidden() {
|
||
mockMvc.perform(post("/login")
|
||
.accept(MediaType.TEXT_HTML)
|
||
.param("username", "user")
|
||
.param("password", "password"))
|
||
.andExpect(status().isForbidden)
|
||
}
|
||
|
||
@Test
|
||
@WithMockUser
|
||
@Throws(Exception::class)
|
||
fun logoutWhenValidCsrfTokenThenSuccess() {
|
||
mockMvc.perform(post("/logout").with(csrf())
|
||
.accept(MediaType.TEXT_HTML))
|
||
.andExpect(status().is3xxRedirection)
|
||
.andExpect(header().string(HttpHeaders.LOCATION, "/login?logout"))
|
||
}
|
||
}
|
||
----
|
||
======
|
||
|
||
[[disable-csrf]]
|
||
== Disable CSRF Protection
|
||
|
||
By default, CSRF protection is enabled, which affects <<csrf-integration,integrating with the backend>> and <<csrf-testing,testing>> your application.
|
||
Before disabling CSRF protection, consider whether it xref:features/exploits/csrf.adoc#csrf-when[makes sense for your application].
|
||
|
||
You can also consider whether only certain endpoints do not require CSRF protection and configure an ignoring rule, as in the following example:
|
||
|
||
[[disable-csrf-ignoring-configuration]]
|
||
.Ignoring Requests
|
||
[tabs]
|
||
======
|
||
Java::
|
||
+
|
||
[source,java,role="primary"]
|
||
----
|
||
@Configuration
|
||
@EnableWebSecurity
|
||
public class SecurityConfig {
|
||
|
||
@Bean
|
||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||
http
|
||
// ...
|
||
.csrf((csrf) -> csrf
|
||
.ignoringRequestMatchers("/api/*")
|
||
);
|
||
return http.build();
|
||
}
|
||
}
|
||
----
|
||
|
||
Kotlin::
|
||
+
|
||
[source,kotlin,role="secondary"]
|
||
----
|
||
import org.springframework.security.config.annotation.web.invoke
|
||
|
||
@Configuration
|
||
@EnableWebSecurity
|
||
class SecurityConfig {
|
||
|
||
@Bean
|
||
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
|
||
http {
|
||
// ...
|
||
csrf {
|
||
ignoringRequestMatchers("/api/*")
|
||
}
|
||
}
|
||
return http.build()
|
||
}
|
||
}
|
||
----
|
||
|
||
XML::
|
||
+
|
||
[source,xml,role="secondary"]
|
||
----
|
||
<http>
|
||
<!-- ... -->
|
||
<csrf request-matcher-ref="csrfMatcher"/>
|
||
</http>
|
||
<b:bean id="csrfMatcher"
|
||
class="org.springframework.security.web.util.matcher.AndRequestMatcher">
|
||
<b:constructor-arg value="#{T(org.springframework.security.web.csrf.CsrfFilter).DEFAULT_CSRF_MATCHER}"/>
|
||
<b:constructor-arg>
|
||
<b:bean class="org.springframework.security.web.util.matcher.NegatedRequestMatcher">
|
||
<b:bean class="org.springframework.security.web.util.matcher.AntPathRequestMatcher">
|
||
<b:constructor-arg value="/api/*"/>
|
||
</b:bean>
|
||
</b:bean>
|
||
</b:constructor-arg>
|
||
</b:bean>
|
||
----
|
||
======
|
||
|
||
If you need to disable CSRF protection, you can do so using the following configuration:
|
||
|
||
[[disable-csrf-configuration]]
|
||
.Disable CSRF
|
||
[tabs]
|
||
======
|
||
Java::
|
||
+
|
||
[source,java,role="primary"]
|
||
----
|
||
@Configuration
|
||
@EnableWebSecurity
|
||
public class SecurityConfig {
|
||
|
||
@Bean
|
||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||
http
|
||
// ...
|
||
.csrf((csrf) -> csrf.disable());
|
||
return http.build();
|
||
}
|
||
}
|
||
----
|
||
|
||
Kotlin::
|
||
+
|
||
[source,kotlin,role="secondary"]
|
||
----
|
||
import org.springframework.security.config.annotation.web.invoke
|
||
|
||
@Configuration
|
||
@EnableWebSecurity
|
||
class SecurityConfig {
|
||
|
||
@Bean
|
||
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
|
||
http {
|
||
// ...
|
||
csrf {
|
||
disable()
|
||
}
|
||
}
|
||
return http.build()
|
||
}
|
||
}
|
||
----
|
||
|
||
XML::
|
||
+
|
||
[source,xml,role="secondary"]
|
||
----
|
||
<http>
|
||
<!-- ... -->
|
||
<csrf disabled="true"/>
|
||
</http>
|
||
----
|
||
======
|
||
|
||
[[csrf-considerations]]
|
||
== CSRF Considerations
|
||
|
||
There are a few special considerations when implementing protection against CSRF attacks.
|
||
This section discusses those considerations as they pertain to servlet environments.
|
||
See xref:features/exploits/csrf.adoc#csrf-considerations[CSRF Considerations] for a more general discussion.
|
||
|
||
[[csrf-considerations-login]]
|
||
=== Logging In
|
||
|
||
It is important to xref:features/exploits/csrf.adoc#csrf-considerations-login[require CSRF for log in] requests to protect against forging log in attempts.
|
||
Spring Security's servlet support does this out of the box.
|
||
|
||
[[csrf-considerations-logout]]
|
||
=== Logging Out
|
||
|
||
It is important to xref:features/exploits/csrf.adoc#csrf-considerations-logout[require CSRF for log out] requests to protect against forging logout attempts.
|
||
If CSRF protection is enabled (the default), Spring Security's `LogoutFilter` will only process HTTP POST requests.
|
||
This ensures that logging out requires a CSRF token and that a malicious user cannot forcibly log your users out.
|
||
|
||
The easiest approach is to use a form to log the user out.
|
||
If you really want a link, you can use JavaScript to have the link perform a POST (maybe on a hidden form).
|
||
For browsers with JavaScript that is disabled, you can optionally have the link take the user to a log out confirmation page that performs the POST.
|
||
|
||
If you really want to use HTTP GET with logout, you can do so.
|
||
However, remember that this is generally not recommended.
|
||
For example, the following logs out when the `/logout` URL is requested with any HTTP method:
|
||
|
||
.Log Out with Any HTTP Method
|
||
[tabs]
|
||
======
|
||
Java::
|
||
+
|
||
[source,java,role="primary"]
|
||
----
|
||
@Configuration
|
||
@EnableWebSecurity
|
||
public class SecurityConfig {
|
||
|
||
@Bean
|
||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||
http
|
||
// ...
|
||
.logout((logout) -> logout
|
||
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
|
||
);
|
||
return http.build();
|
||
}
|
||
}
|
||
----
|
||
|
||
Kotlin::
|
||
+
|
||
[source,kotlin,role="secondary"]
|
||
----
|
||
import org.springframework.security.config.annotation.web.invoke
|
||
|
||
@Configuration
|
||
@EnableWebSecurity
|
||
class SecurityConfig {
|
||
|
||
@Bean
|
||
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
|
||
http {
|
||
// ...
|
||
logout {
|
||
logoutRequestMatcher = AntPathRequestMatcher("/logout")
|
||
}
|
||
}
|
||
return http.build()
|
||
}
|
||
}
|
||
----
|
||
======
|
||
|
||
See the xref:servlet/authentication/logout.adoc[Logout] chapter for more information.
|
||
|
||
[[considerations-csrf-timeouts]]
|
||
=== CSRF and Session Timeouts
|
||
|
||
By default, Spring Security stores the CSRF token in the `HttpSession` using the <<csrf-token-repository-httpsession,`HttpSessionCsrfTokenRepository`>>.
|
||
This can lead to a situation where the session expires, leaving no CSRF token to validate against.
|
||
|
||
We have already discussed xref:features/exploits/csrf.adoc#csrf-considerations-timeouts[general solutions] to session timeouts.
|
||
This section discusses the specifics of CSRF timeouts as it pertains to the servlet support.
|
||
|
||
You can change the storage of the CSRF token to be in a cookie.
|
||
For details, see the <<csrf-token-repository-cookie>> section.
|
||
|
||
If a token does expire, you might want to customize how it is handled by specifying a <<csrf-access-denied-handler,custom `AccessDeniedHandler`>>.
|
||
The custom `AccessDeniedHandler` can process the `InvalidCsrfTokenException` any way you like.
|
||
|
||
[[csrf-considerations-multipart]]
|
||
=== Multipart (file upload)
|
||
|
||
We have xref:features/exploits/csrf.adoc#csrf-considerations-multipart[already discussed] how protecting multipart requests (file uploads) from CSRF attacks causes a https://en.wikipedia.org/wiki/Chicken_or_the_egg[chicken and the egg] problem.
|
||
When JavaScript is available, we _recommend_ <<csrf-integration-javascript-other,including the CSRF token in an HTTP request header>> to side-step the issue.
|
||
|
||
If JavaScript is not available, the following sections discuss options for placing the CSRF token in the <<csrf-considerations-multipart-body,body>> and <<csrf-considerations-multipart-url,url>> within a servlet application.
|
||
|
||
[NOTE]
|
||
====
|
||
You can find more information about using multipart forms with Spring in the https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-multipart[Multipart Resolver] section of the Spring reference and the {spring-framework-api-url}org/springframework/web/multipart/support/MultipartFilter.html[`MultipartFilter` javadoc].
|
||
====
|
||
|
||
[[csrf-considerations-multipart-body]]
|
||
==== Place CSRF Token in the Body
|
||
|
||
We have xref:features/exploits/csrf.adoc#csrf-considerations-multipart-body[already discussed] the tradeoffs of placing the CSRF token in the body.
|
||
In this section, we discuss how to configure Spring Security to read the CSRF from the body.
|
||
|
||
To read the CSRF token from the body, the `MultipartFilter` is specified before the Spring Security filter.
|
||
Specifying the `MultipartFilter` before the Spring Security filter means that there is no authorization for invoking the `MultipartFilter`, which means anyone can place temporary files on your server.
|
||
However, only authorized users can submit a file that is processed by your application.
|
||
In general, this is the recommended approach because the temporary file upload should have a negligible impact on most servers.
|
||
|
||
.Configure `MultipartFilter`
|
||
[tabs]
|
||
======
|
||
Java::
|
||
+
|
||
[source,java,role="primary"]
|
||
----
|
||
public class SecurityApplicationInitializer extends AbstractSecurityWebApplicationInitializer {
|
||
|
||
@Override
|
||
protected void beforeSpringSecurityFilterChain(ServletContext servletContext) {
|
||
insertFilters(servletContext, new MultipartFilter());
|
||
}
|
||
}
|
||
----
|
||
|
||
Kotlin::
|
||
+
|
||
[source,kotlin,role="secondary"]
|
||
----
|
||
class SecurityApplicationInitializer : AbstractSecurityWebApplicationInitializer() {
|
||
override fun beforeSpringSecurityFilterChain(servletContext: ServletContext?) {
|
||
insertFilters(servletContext, MultipartFilter())
|
||
}
|
||
}
|
||
----
|
||
|
||
XML::
|
||
+
|
||
[source,xml,role="secondary"]
|
||
----
|
||
<filter>
|
||
<filter-name>MultipartFilter</filter-name>
|
||
<filter-class>org.springframework.web.multipart.support.MultipartFilter</filter-class>
|
||
</filter>
|
||
<filter>
|
||
<filter-name>springSecurityFilterChain</filter-name>
|
||
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
|
||
</filter>
|
||
<filter-mapping>
|
||
<filter-name>MultipartFilter</filter-name>
|
||
<url-pattern>/*</url-pattern>
|
||
</filter-mapping>
|
||
<filter-mapping>
|
||
<filter-name>springSecurityFilterChain</filter-name>
|
||
<url-pattern>/*</url-pattern>
|
||
</filter-mapping>
|
||
----
|
||
======
|
||
|
||
[NOTE]
|
||
====
|
||
To ensure that `MultipartFilter` is specified before the Spring Security filter with XML configuration, you can ensure the `<filter-mapping>` element of the `MultipartFilter` is placed before the `springSecurityFilterChain` within the `web.xml` file.
|
||
====
|
||
|
||
[[csrf-considerations-multipart-url]]
|
||
==== Include a CSRF Token in a URL
|
||
|
||
If letting unauthorized users upload temporary files is not acceptable, an alternative is to place the `MultipartFilter` after the Spring Security filter and include the CSRF as a query parameter in the action attribute of the form.
|
||
Since the `CsrfToken` is exposed as an <<csrf-token-request-handler,`HttpServletRequest` attribute named `_csrf`>>, we can use that to create an `action` with the CSRF token in it.
|
||
The following example does this with a JSP:
|
||
|
||
.CSRF Token in Action
|
||
[source,html]
|
||
----
|
||
<form method="post"
|
||
action="./upload?${_csrf.parameterName}=${_csrf.token}"
|
||
enctype="multipart/form-data">
|
||
----
|
||
|
||
[[csrf-considerations-override-method]]
|
||
=== HiddenHttpMethodFilter
|
||
|
||
We have xref:features/exploits/csrf.adoc#csrf-considerations-multipart-body[already discussed] the trade-offs of placing the CSRF token in the body.
|
||
|
||
In Spring's Servlet support, overriding the HTTP method is done by using {spring-framework-api-url}org/springframework/web/filter/reactive/HiddenHttpMethodFilter.html[`HiddenHttpMethodFilter`].
|
||
You can find more information in the https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-rest-method-conversion[HTTP Method Conversion] section of the reference documentation.
|
||
|
||
[[csrf-further-reading]]
|
||
== Further Reading
|
||
|
||
Now that you have reviewed CSRF protection, consider learning more about xref:servlet/exploits/index.adoc[exploit protection] including xref:servlet/exploits/headers.adoc[secure headers] and the xref:servlet/exploits/firewall.adoc[HTTP firewall] or move on to learning how to xref:servlet/test/index.adoc[test] your application.
|