mirror of
				https://github.com/spring-projects/spring-security.git
				synced 2025-11-04 00:28:54 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			413 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
			
		
		
	
	
			413 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
[[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-idempotent,Use proper HTTP verbs>>
 | 
						|
* <<webflux-csrf-configure,Configure CSRF Protection>>
 | 
						|
* <<webflux-csrf-include,Include the CSRF Token>>
 | 
						|
 | 
						|
[[webflux-csrf-idempotent]]
 | 
						|
=== 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-idempotent[Safe Methods Must be Idempotent].
 | 
						|
 | 
						|
[[webflux-csrf-configure]]
 | 
						|
=== Configure CSRF Protection
 | 
						|
The next step is to configure Spring Security's CSRF protection within your application.
 | 
						|
Spring Security's CSRF protection is enabled by default, but you may need to customize the configuration.
 | 
						|
Below are a few common customizations.
 | 
						|
 | 
						|
[[webflux-csrf-configure-custom-repository]]
 | 
						|
==== Custom CsrfTokenRepository
 | 
						|
 | 
						|
By default Spring Security stores the expected CSRF token in the `WebSession` using `WebSessionServerCsrfTokenRepository`.
 | 
						|
There can be cases where users will want to configure a custom `ServerCsrfTokenRepository`.
 | 
						|
For example, it might be desirable to persist the `CsrfToken` in a cookie to <<webflux-csrf-include-ajax-auto,support a JavaScript based application>>.
 | 
						|
 | 
						|
By default the `CookieServerCsrfTokenRepository` will write to a cookie named `XSRF-TOKEN` and read it from a header named `X-XSRF-TOKEN` or the HTTP parameter `_csrf`.
 | 
						|
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 using:
 | 
						|
 | 
						|
.Store CSRF Token in a Cookie
 | 
						|
====
 | 
						|
.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 sample explicitly sets `cookieHttpOnly=false`.
 | 
						|
This is necessary to allow JavaScript (i.e. AngularJS) to read it.
 | 
						|
If you do not need the ability to read the cookie with JavaScript directly, it is recommended to omit `cookieHttpOnly=false` (by using `new CookieServerCsrfTokenRepository()` instead) to improve security.
 | 
						|
====
 | 
						|
 | 
						|
[[webflux-csrf-configure-disable]]
 | 
						|
==== Disable CSRF Protection
 | 
						|
CSRF protection is enabled by default.
 | 
						|
However, it is simple to 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
 | 
						|
====
 | 
						|
.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-include]]
 | 
						|
=== Include the CSRF Token
 | 
						|
 | 
						|
In order 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 (i.e. form parameter, HTTP header, etc) that is not automatically included in the HTTP request by the browser.
 | 
						|
 | 
						|
Spring Security's https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/web/server/csrf/CsrfWebFilter.html[CsrfWebFilter] exposes a https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/web/csrf/CsrfToken.html[Mono<CsrfToken>] as a `ServerWebExchange` attribute named `org.springframework.security.web.server.csrf.CsrfToken`.
 | 
						|
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 <<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.
 | 
						|
For example, the following code will place 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`
 | 
						|
====
 | 
						|
.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 -> exchange.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
 | 
						|
In order to post 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 HTML
 | 
						|
====
 | 
						|
[source,html]
 | 
						|
----
 | 
						|
<input type="hidden"
 | 
						|
	name="_csrf"
 | 
						|
	value="4bfd1575-3ad1-4d21-96c7-4ef2d9f86721"/>
 | 
						|
----
 | 
						|
====
 | 
						|
 | 
						|
Next we will 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] via its https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/web/reactive/result/view/CsrfRequestDataValueProcessor.html[CsrfRequestDataValueProcessor].
 | 
						|
In order 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 https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/web/reactive/result/view/CsrfRequestDataValueProcessor.html#DEFAULT_CSRF_ATTR_NAME[DEFAULT_CSRF_ATTR_NAME].
 | 
						|
 | 
						|
Fortunately, Thymeleaf https://www.thymeleaf.org/doc/tutorials/2.1/thymeleafspring.html#integration-with-requestdatavalueprocessor[provides support] to take care of all the boilerplate for you by integrating with `RequestDataValueProcessor` to ensure that forms that have an unsafe HTTP method (i.e. post) will 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 Thymeleaf sample below 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 are using JSON, then it is not possible to submit the CSRF token within an HTTP parameter.
 | 
						|
Instead you can submit the token within a HTTP header.
 | 
						|
 | 
						|
In the following sections we will discuss various ways of including the CSRF token as an HTTP request header in JavaScript based applications.
 | 
						|
 | 
						|
[[webflux-csrf-include-ajax-auto]]
 | 
						|
===== Automatic Inclusion
 | 
						|
 | 
						|
Spring Security can easily be <<webflux-csrf-configure-custom-repository,configured>> to store the expected CSRF token in a cookie.
 | 
						|
By storing the expected CSRF in a cookie, JavaScript frameworks like https://docs.angularjs.org/api/ng/service/$http#cross-site-request-forgery-xsrf-protection[AngularJS] will 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 contained the CSRF token, the JavaScript code would read the meta tags and include the CSRF token as a header.
 | 
						|
If you were using jQuery, this could be done with the following:
 | 
						|
 | 
						|
.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 sample below assumes that you <<webflux-csrf-include-subscribe,expose>> the `CsrfToken` on an attribute named `_csrf`.
 | 
						|
An example of doing this with Thymeleaf is shown below:
 | 
						|
 | 
						|
.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.
 | 
						|
Refer to xref:features/exploits/csrf.adoc#csrf-considerations[CSRF Considerations] for a more general discussion.
 | 
						|
 | 
						|
 | 
						|
[[webflux-considerations-csrf-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 WebFlux support does this out of the box.
 | 
						|
 | 
						|
[[webflux-considerations-csrf-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 log out attempts.
 | 
						|
By default Spring Security's `LogoutWebFilter` only processes HTTP post requests.
 | 
						|
This ensures that log out 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 (i.e. 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 will perform the POST.
 | 
						|
 | 
						|
If you really want to use HTTP GET with logout you can do so, but remember this is generally not recommended.
 | 
						|
For example, the following Java Configuration will perform logout with the URL `/logout` is requested with any HTTP method:
 | 
						|
 | 
						|
// FIXME: This should be a link to log out documentation
 | 
						|
 | 
						|
.Log out with HTTP GET
 | 
						|
====
 | 
						|
.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 can lead to a situation where the session expires which means there is not an expected CSRF token to validate against.
 | 
						|
 | 
						|
We've 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.
 | 
						|
 | 
						|
It is simple to change storage of the expected CSRF token to be in a cookie.
 | 
						|
For details, refer to the <<webflux-csrf-configure-custom-repository>> section.
 | 
						|
 | 
						|
// FIXME: We should add a custom AccessDeniedHandler section in the reference and update the links above
 | 
						|
 | 
						|
// 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]
 | 
						|
====
 | 
						|
More information about using multipart forms with Spring can be found within 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, this can be configured with the following configuration:
 | 
						|
 | 
						|
.Enable obtaining CSRF token from multipart/form-data
 | 
						|
====
 | 
						|
.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 using https://docs.spring.io/spring-framework/docs/5.2.x/javadoc-api/org/springframework/web/filter/reactive/HiddenHttpMethodFilter.html[HiddenHttpMethodFilter].
 |