You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1543 lines
50 KiB
1543 lines
50 KiB
[[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 javadoc:org.springframework.security.web.csrf.CsrfFilter[]: |
|
|
|
.`CsrfFilter` Components |
|
[.invert-dark] |
|
image::{figures}/csrf.png[] |
|
|
|
CSRF protection is divided into two parts: |
|
|
|
1. Make the javadoc:org.springframework.security.web.csrf.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 |
|
[.invert-dark] |
|
image::{figures}/csrf-processing.png[] |
|
|
|
* image:{icondir}/number_1.png[] First, the javadoc:org.springframework.security.web.csrf.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 javadoc:org.springframework.security.web.csrf.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 javadoc:org.springframework.security.web.csrf.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.dev/best-practices/security#httpclient-xsrf-csrf-security[HttpClient XSRF/CSRF security] and the https://angular.dev/api/common/http/withXsrfConfiguration[withXsrfConfiguration] 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 javadoc:org.springframework.security.web.csrf.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 javadoc:org.springframework.security.web.servlet.support.csrf.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 javadoc:org.springframework.security.web.csrf.CsrfAuthenticationStrategy[] and javadoc:org.springframework.security.web.csrf.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.spa()); |
|
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 { |
|
spa() |
|
} |
|
} |
|
return http.build() |
|
} |
|
} |
|
---- |
|
|
|
XML:: |
|
+ |
|
[source,xml,role="secondary"] |
|
---- |
|
<http> |
|
<!-- ... --> |
|
<csrf> |
|
<spa /> |
|
</csrf> |
|
</http> |
|
---- |
|
====== |
|
|
|
[[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 javadoc:org.springframework.security.web.csrf.CsrfAuthenticationStrategy[] and javadoc:org.springframework.security.web.csrf.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.config.http.PathPatternRequestMatcherFactoryBean"> |
|
<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(PathPatternRequestMatcher.withDefaults().matcher("/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 = PathPatternRequestMatcher.withDefaults().matcher("/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.
|
|
|