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.
459 lines
17 KiB
459 lines
17 KiB
[[webflux-csrf]] |
|
= Cross Site Request Forgery (CSRF) for WebFlux Environments |
|
|
|
This section discusses Spring Security's xref:features/exploits/csrf.adoc#csrf[Cross Site Request Forgery (CSRF)] support for WebFlux environments. |
|
|
|
[[webflux-csrf-using]] |
|
== Using Spring Security CSRF Protection |
|
The steps to using Spring Security's CSRF protection are outlined below: |
|
|
|
* <<webflux-csrf-read-only,Use proper HTTP verbs>> |
|
* <<webflux-csrf-configure,Configure CSRF Protection>> |
|
* <<webflux-csrf-include,Include the CSRF Token>> |
|
|
|
[[webflux-csrf-read-only]] |
|
=== Use Proper HTTP Verbs |
|
The first step to protecting against CSRF attacks is to ensure your website uses proper HTTP verbs. |
|
This is covered in detail in xref:features/exploits/csrf.adoc#csrf-protection-read-only[Safe Methods Must be Read-only]. |
|
|
|
[[webflux-csrf-configure]] |
|
=== Configure CSRF Protection |
|
The next step is to configure Spring Security's CSRF protection within your application. |
|
By default, Spring Security's CSRF protection is enabled, but you may need to customize the configuration. |
|
The next few subsections cover a few common customizations. |
|
|
|
[[webflux-csrf-configure-custom-repository]] |
|
==== Custom CsrfTokenRepository |
|
|
|
By default, Spring Security stores the expected CSRF token in the `WebSession` by using `WebSessionServerCsrfTokenRepository`. |
|
Sometimes, you may need to configure a custom `ServerCsrfTokenRepository`. |
|
For example, you may want to persist the `CsrfToken` in a cookie to <<webflux-csrf-include-ajax-auto,support a JavaScript-based application>>. |
|
|
|
By default, the `CookieServerCsrfTokenRepository` writes to a cookie named `XSRF-TOKEN` and read its from a header named `X-XSRF-TOKEN` or the HTTP `_csrf` parameter. |
|
These defaults come from https://docs.angularjs.org/api/ng/service/$http#cross-site-request-forgery-xsrf-protection[AngularJS] |
|
|
|
You can configure `CookieServerCsrfTokenRepository` in Java Configuration: |
|
|
|
.Store CSRF Token in a Cookie |
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,role="primary"] |
|
----- |
|
@Bean |
|
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { |
|
http |
|
// ... |
|
.csrf((csrf) -> csrf.csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse())) |
|
return http.build(); |
|
} |
|
----- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,role="secondary"] |
|
----- |
|
@Bean |
|
fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain { |
|
return http { |
|
// ... |
|
csrf { |
|
csrfTokenRepository = CookieServerCsrfTokenRepository.withHttpOnlyFalse() |
|
} |
|
} |
|
} |
|
----- |
|
====== |
|
|
|
[NOTE] |
|
==== |
|
The preceding sample explicitly sets `cookieHttpOnly=false`. |
|
This is necessary to let JavaScript (in this case, AngularJS) to read it. |
|
If you do not need the ability to read the cookie with JavaScript directly, we recommend to omitting `cookieHttpOnly=false` (by using `new CookieServerCsrfTokenRepository()` instead) to improve security. |
|
==== |
|
|
|
[[webflux-csrf-configure-disable]] |
|
==== Disable CSRF Protection |
|
By default, CSRF protection is enabled. |
|
However, you can disable CSRF protection if it xref:features/exploits/csrf.adoc#csrf-when[makes sense for your application]. |
|
|
|
The Java configuration below will disable CSRF protection. |
|
|
|
.Disable CSRF Configuration |
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,role="primary"] |
|
---- |
|
@Bean |
|
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { |
|
http |
|
// ... |
|
.csrf((csrf) -> csrf.disable())) |
|
return http.build(); |
|
} |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,role="secondary"] |
|
----- |
|
@Bean |
|
fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain { |
|
return http { |
|
// ... |
|
csrf { |
|
disable() |
|
} |
|
} |
|
} |
|
----- |
|
====== |
|
|
|
[[webflux-csrf-configure-request-handler]] |
|
==== Configure ServerCsrfTokenRequestHandler |
|
|
|
Spring Security's javadoc:org.springframework.security.web.server.csrf.CsrfWebFilter[] exposes a javadoc:org.springframework.security.web.server.csrf.CsrfToken[`Mono<CsrfToken>`] as a `ServerWebExchange` attribute named `org.springframework.security.web.server.csrf.CsrfToken` with the help of a javadoc:org.springframework.security.web.server.csrf.ServerCsrfTokenRequestHandler[]. |
|
In 5.8, the default implementation was `ServerCsrfTokenRequestAttributeHandler`, which simply makes the `Mono<CsrfToken>` available as an exchange attribute. |
|
|
|
As of 6.0, the default implementation is `XorServerCsrfTokenRequestAttributeHandler`, which provides protection for BREACH (see https://github.com/spring-projects/spring-security/issues/4001[gh-4001]). |
|
|
|
If you wish to disable BREACH protection of the `CsrfToken` and revert to the 5.8 default, you can configure `ServerCsrfTokenRequestAttributeHandler` using the following Java configuration: |
|
|
|
.Disable BREACH protection |
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,role="primary"] |
|
----- |
|
@Bean |
|
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { |
|
http |
|
// ... |
|
.csrf((csrf) -> csrf |
|
.csrfTokenRequestHandler(new ServerCsrfTokenRequestAttributeHandler()) |
|
) |
|
return http.build(); |
|
} |
|
----- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,role="secondary"] |
|
----- |
|
@Bean |
|
fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain { |
|
return http { |
|
// ... |
|
csrf { |
|
csrfTokenRequestHandler = ServerCsrfTokenRequestAttributeHandler() |
|
} |
|
} |
|
} |
|
----- |
|
====== |
|
|
|
[[webflux-csrf-include]] |
|
=== Include the CSRF Token |
|
|
|
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. |
|
It must be included in a part of the request (a form parameter, an HTTP header, or other option) that is not automatically included in the HTTP request by the browser. |
|
|
|
<<webflux-csrf-configure-request-handler,We've seen>> that the `Mono<CsrfToken>` is exposed as a `ServerWebExchange` attribute. |
|
This means that any view technology can access the `Mono<CsrfToken>` to expose the expected token as either a <<webflux-csrf-include-form-attr,form>> or a <<webflux-csrf-include-ajax-meta,meta tag>>. |
|
|
|
[[webflux-csrf-include-subscribe]] |
|
If your view technology does not provide a simple way to subscribe to the `Mono<CsrfToken>`, a common pattern is to use Spring's `@ControllerAdvice` to expose the `CsrfToken` directly. |
|
The following example places the `CsrfToken` on the default attribute name (`_csrf`) used by Spring Security's <<webflux-csrf-include-form-auto,CsrfRequestDataValueProcessor>> to automatically include the CSRF token as a hidden input: |
|
|
|
.`CsrfToken` as `@ModelAttribute` |
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,role="primary"] |
|
---- |
|
@ControllerAdvice |
|
public class SecurityControllerAdvice { |
|
@ModelAttribute |
|
Mono<CsrfToken> csrfToken(ServerWebExchange exchange) { |
|
Mono<CsrfToken> csrfToken = exchange.getAttribute(CsrfToken.class.getName()); |
|
return csrfToken.doOnSuccess((token) -> token.getAttributes() |
|
.put(CsrfRequestDataValueProcessor.DEFAULT_CSRF_ATTR_NAME, token)); |
|
} |
|
} |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,role="secondary"] |
|
---- |
|
@ControllerAdvice |
|
class SecurityControllerAdvice { |
|
@ModelAttribute |
|
fun csrfToken(exchange: ServerWebExchange): Mono<CsrfToken> { |
|
val csrfToken: Mono<CsrfToken>? = exchange.getAttribute(CsrfToken::class.java.name) |
|
return csrfToken!!.doOnSuccess { token -> |
|
exchange.attributes[CsrfRequestDataValueProcessor.DEFAULT_CSRF_ATTR_NAME] = token |
|
} |
|
} |
|
} |
|
---- |
|
====== |
|
|
|
Fortunately, Thymeleaf provides <<webflux-csrf-include-form-auto,integration>> that works without any additional work. |
|
|
|
[[webflux-csrf-include-form]] |
|
==== Form URL Encoded |
|
To post an HTML form, the CSRF token must be included in the form as a hidden input. |
|
The following example shows what the rendered HTML might look like: |
|
|
|
.CSRF Token HTML |
|
[source,html] |
|
---- |
|
<input type="hidden" |
|
name="_csrf" |
|
value="4bfd1575-3ad1-4d21-96c7-4ef2d9f86721"/> |
|
---- |
|
|
|
Next, we discuss various ways of including the CSRF token in a form as a hidden input. |
|
|
|
[[webflux-csrf-include-form-auto]] |
|
===== Automatic CSRF Token Inclusion |
|
|
|
Spring Security's CSRF support provides integration with Spring's https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/reactive/result/view/RequestDataValueProcessor.html[`RequestDataValueProcessor`] through its javadoc:org.springframework.security.web.reactive.result.view.CsrfRequestDataValueProcessor[]. |
|
For `CsrfRequestDataValueProcessor` to work, the `Mono<CsrfToken>` must be subscribed to and the `CsrfToken` must be <<webflux-csrf-include-subscribe,exposed as an attribute>> that matches javadoc:org.springframework.security.web.reactive.result.view.CsrfRequestDataValueProcessor#DEFAULT_CSRF_ATTR_NAME[]. |
|
|
|
Fortunately, Thymeleaf https://www.thymeleaf.org/doc/tutorials/2.1/thymeleafspring.html#integration-with-requestdatavalueprocessor[takes care of all the boilerplate] for you by integrating with `RequestDataValueProcessor` to ensure that forms that have an unsafe HTTP method (POST) automatically include the actual CSRF token. |
|
|
|
[[webflux-csrf-include-form-attr]] |
|
===== CsrfToken Request Attribute |
|
|
|
If the <<webflux-csrf-include,other options>> for including the actual CSRF token in the request do not work, you can take advantage of the fact that the `Mono<CsrfToken>` <<webflux-csrf-include,is exposed>> as a `ServerWebExchange` attribute named `org.springframework.security.web.server.csrf.CsrfToken`. |
|
|
|
The following Thymeleaf sample assumes that you <<webflux-csrf-include-subscribe,expose>> the `CsrfToken` on an attribute named `_csrf`: |
|
|
|
.CSRF Token in Form with Request Attribute |
|
[source,html] |
|
---- |
|
<form th:action="@{/logout}" |
|
method="post"> |
|
<input type="submit" |
|
value="Log out" /> |
|
<input type="hidden" |
|
th:name="${_csrf.parameterName}" |
|
th:value="${_csrf.token}"/> |
|
</form> |
|
---- |
|
|
|
[[webflux-csrf-include-ajax]] |
|
==== Ajax and JSON Requests |
|
If you use JSON, you cannot submit the CSRF token within an HTTP parameter. |
|
Instead, you can submit the token within a HTTP header. |
|
|
|
In the following sections, we discuss various ways of including the CSRF token as an HTTP request header in JavaScript-based applications. |
|
|
|
[[webflux-csrf-include-ajax-auto]] |
|
===== Automatic Inclusion |
|
|
|
You can <<webflux-csrf-configure-custom-repository,configure>> Spring Security to store the expected CSRF token in a cookie. |
|
By storing the expected CSRF in a cookie, JavaScript frameworks, such as https://docs.angularjs.org/api/ng/service/$http#cross-site-request-forgery-xsrf-protection[AngularJS], automatically include the actual CSRF token in the HTTP request headers. |
|
|
|
[[webflux-csrf-include-ajax-meta]] |
|
===== Meta Tags |
|
|
|
An alternative pattern to <<webflux-csrf-include-form-auto,exposing the CSRF in a cookie>> is to include the CSRF token within your `meta` tags. |
|
The HTML might look something like this: |
|
|
|
.CSRF meta tag HTML |
|
[source,html] |
|
---- |
|
<html> |
|
<head> |
|
<meta name="_csrf" content="4bfd1575-3ad1-4d21-96c7-4ef2d9f86721"/> |
|
<meta name="_csrf_header" content="X-CSRF-TOKEN"/> |
|
<!-- ... --> |
|
</head> |
|
<!-- ... --> |
|
---- |
|
|
|
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 could read the meta tags with the following code: |
|
|
|
.AJAX send CSRF Token |
|
[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); |
|
}); |
|
}); |
|
---- |
|
|
|
The following sample assumes that you <<webflux-csrf-include-subscribe,expose>> the `CsrfToken` on an attribute named `_csrf`. |
|
The following example does this with Thymeleaf: |
|
|
|
.CSRF meta tag JSP |
|
[source,html] |
|
---- |
|
<html> |
|
<head> |
|
<meta name="_csrf" th:content="${_csrf.token}"/> |
|
<!-- default header name is X-CSRF-TOKEN --> |
|
<meta name="_csrf_header" th:content="${_csrf.headerName}"/> |
|
<!-- ... --> |
|
</head> |
|
<!-- ... --> |
|
---- |
|
|
|
[[webflux-csrf-considerations]] |
|
== CSRF Considerations |
|
There are a few special considerations to consider when implementing protection against CSRF attacks. |
|
This section discusses those considerations as it pertains to WebFlux environments. |
|
See xref:features/exploits/csrf.adoc#csrf-considerations[CSRF Considerations] for a more general discussion. |
|
|
|
|
|
[[webflux-considerations-csrf-login]] |
|
=== Logging In |
|
|
|
You should xref:features/exploits/csrf.adoc#csrf-considerations-login[require CSRF for login] requests to protect against forged login attempts. |
|
Spring Security's WebFlux support automatically does this. |
|
|
|
[[webflux-considerations-csrf-logout]] |
|
=== Logging Out |
|
|
|
You should xref:features/exploits/csrf.adoc#csrf-considerations-logout[require CSRF for logout] requests to protect against forging logout attempts. |
|
By default, Spring Security's `LogoutWebFilter` only processes only HTTP post requests. |
|
This ensures that logout requires a CSRF token and that a malicious user cannot forcibly log out your users. |
|
|
|
The easiest approach is to use a form to log 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 logout confirmation page that performs the POST. |
|
|
|
If you really want to use HTTP GET with logout, you can do so, but remember that doing so is generally not recommended. |
|
For example, the following Java Configuration logs out when the `/logout` URL is requested with any HTTP method: |
|
|
|
// FIXME: This should be a link to log out documentation |
|
|
|
.Log out with HTTP GET |
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,role="primary"] |
|
---- |
|
@Bean |
|
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { |
|
http |
|
// ... |
|
.logout((logout) -> logout.requiresLogout(new PathPatternParserServerWebExchangeMatcher("/logout"))) |
|
return http.build(); |
|
} |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,role="secondary"] |
|
---- |
|
@Bean |
|
fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain { |
|
return http { |
|
// ... |
|
logout { |
|
requiresLogout = PathPatternParserServerWebExchangeMatcher("/logout") |
|
} |
|
} |
|
} |
|
---- |
|
====== |
|
|
|
|
|
[[webflux-considerations-csrf-timeouts]] |
|
=== CSRF and Session Timeouts |
|
|
|
By default, Spring Security stores the CSRF token in the `WebSession`. |
|
This arrangement can lead to a situation where the session expires, which means that there is not an expected CSRF token to validate against. |
|
|
|
We have already discussed xref:features/exploits/csrf.adoc#csrf-considerations-login[general solutions] to session timeouts. |
|
This section discusses the specifics of CSRF timeouts as it pertains to the WebFlux support. |
|
|
|
You can change storage of the expected CSRF token to be in a cookie. |
|
For details, see the <<webflux-csrf-configure-custom-repository>> section. |
|
|
|
// FIXME: We should add a custom AccessDeniedHandler section in the reference and update the earlier links |
|
|
|
// FIXME: We need a WebFlux multipart body vs action story. WebFlux always has multipart enabled. |
|
[[webflux-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. |
|
This section discusses how to implement placing the CSRF token in the <<webflux-csrf-considerations-multipart-body,body>> and <<webflux-csrf-considerations-multipart-url,url>> within a WebFlux application. |
|
|
|
[NOTE] |
|
==== |
|
For more information about using multipart forms with Spring, see the https://docs.spring.io/spring/docs/5.2.x/spring-framework-reference/web-reactive.html#webflux-multipart[Multipart Data] section of the Spring reference. |
|
==== |
|
|
|
[[webflux-csrf-considerations-multipart-body]] |
|
==== Place CSRF Token in the Body |
|
|
|
We have xref:features/exploits/csrf.adoc#csrf-considerations-multipart[already discussed] the trade-offs of placing the CSRF token in the body. |
|
|
|
In a WebFlux application, you can do so with the following configuration: |
|
|
|
.Enable obtaining CSRF token from multipart/form-data |
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,role="primary"] |
|
---- |
|
@Bean |
|
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { |
|
http |
|
// ... |
|
.csrf((csrf) -> csrf.tokenFromMultipartDataEnabled(true)) |
|
return http.build(); |
|
} |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,role="secondary"] |
|
---- |
|
@Bean |
|
fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain { |
|
return http { |
|
// ... |
|
csrf { |
|
tokenFromMultipartDataEnabled = true |
|
} |
|
} |
|
} |
|
---- |
|
====== |
|
|
|
[[webflux-csrf-considerations-multipart-url]] |
|
==== Include CSRF Token in URL |
|
|
|
We have xref:features/exploits/csrf.adoc#csrf-considerations-multipart[already discussed] the trade-offs of placing the CSRF token in the URL. |
|
Since the `CsrfToken` is exposed as an `ServerHttpRequest` <<webflux-csrf-include,request attribute>>, we can use that to create an `action` with the CSRF token in it. |
|
An example with Thymeleaf is shown below: |
|
|
|
.CSRF Token in Action |
|
[source,html] |
|
---- |
|
<form method="post" |
|
th:action="@{/upload(${_csrf.parameterName}=${_csrf.token})}" |
|
enctype="multipart/form-data"> |
|
---- |
|
|
|
[[webflux-csrf-considerations-override-method]] |
|
=== HiddenHttpMethodFilter |
|
We have xref:features/exploits/csrf.adoc#csrf-considerations-override-method[already discussed] overriding the HTTP method. |
|
|
|
In a Spring WebFlux application, overriding the HTTP method is done by using https://docs.spring.io/spring-framework/docs/5.2.x/javadoc-api/org/springframework/web/filter/reactive/HiddenHttpMethodFilter.html[`HiddenHttpMethodFilter`].
|
|
|