Skip to content

Commit 2827af1

Browse files
committed
Document Reactive CSRF Support
Fixes gh-6487
1 parent 635f7e1 commit 2827af1

File tree

5 files changed

+343
-6
lines changed

5 files changed

+343
-6
lines changed

docs/manual/src/docs/asciidoc/_includes/about/exploits/csrf.adoc

-5
Original file line numberDiff line numberDiff line change
@@ -411,8 +411,3 @@ Overriding the HTTP method occurs in a filter.
411411
That filter must be placed before Spring Security's support.
412412
Note that overriding only happens on a `post`, so this is actually unlikely to cause any real problems.
413413
However, it is still best practice to ensure it is placed before Spring Security's filters.
414-
415-
In Spring's Servlet support, 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].
416-
More information can be found in https://docs.spring.io/spring/docs/5.2.x/spring-framework-reference/web.html#mvc-rest-method-conversion[HTTP Method Conversion] section of the reference documentation.
417-
418-
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].
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,338 @@
1+
[[webflux-csrf]]
2+
= Cross Site Request Forgery (CSRF) for WebFlux Environments
3+
4+
This section discusses Spring Security's <<csrf,Cross Site Request Forgery (CSRF)>> support for WebFlux environments.
5+
6+
[[webflux-csrf-using]]
7+
== Using Spring Security CSRF Protection
8+
The steps to using Spring Security's CSRF protection are outlined below:
9+
10+
* <<webflux-csrf-idempotent,Use proper HTTP verbs>>
11+
* <<webflux-csrf-configure,Configure CSRF Protection>>
12+
* <<webflux-csrf-include,Include the CSRF Token>>
13+
14+
[[webflux-csrf-idempotent]]
15+
=== Use proper HTTP verbs
16+
The first step to protecting against CSRF attacks is to ensure your website uses proper HTTP verbs.
17+
This is covered in detail in <<csrf-protection-idempotent,Safe Methods Must be Idempotent>>.
18+
19+
[[webflux-csrf-configure]]
20+
=== Configure CSRF Protection
21+
The next step is to configure Spring Security's CSRF protection within your application.
22+
Spring Security's CSRF protection is enabled by default, but you may need to customize the configuration.
23+
Below are a few common customizations.
24+
25+
[[webflux-csrf-configure-custom-repository]]
26+
==== Custom CsrfTokenRepository
27+
28+
By default Spring Security stores the expected CSRF token in the `WebSession` using `WebSessionServerCsrfTokenRepository`.
29+
There can be cases where users will want to configure a custom `ServerCsrfTokenRepository`.
30+
For example, it might be desirable to persist the `CsrfToken` in a cookie to <<webflux-csrf-include-ajax-auto,support a JavaScript based application>>.
31+
32+
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`.
33+
These defaults come from https://docs.angularjs.org/api/ng/service/$http#cross-site-request-forgery-xsrf-protection[AngularJS]
34+
35+
You can configure `CookieCsrfTokenRepository` in Java Configuration using:
36+
37+
.Store CSRF Token in a Cookie with Java Configuration
38+
====
39+
[source,java]
40+
-----
41+
@Bean
42+
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
43+
http
44+
// ...
45+
.csrf(csrf -> csrf.csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse()))
46+
return http.build();
47+
}
48+
-----
49+
====
50+
51+
[NOTE]
52+
====
53+
The sample explicitly sets `cookieHttpOnly=false`.
54+
This is necessary to allow JavaScript (i.e. AngularJS) to read it.
55+
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.
56+
====
57+
58+
[[webflux-csrf-configure-disable]]
59+
==== Disable CSRF Protection
60+
CSRF protection is enabled by default.
61+
However, it is simple to disable CSRF protection if it <<csrf-when,makes sense for your application>>.
62+
63+
The Java configuration below will disable CSRF protection.
64+
65+
.Disable CSRF Java Configuration
66+
====
67+
[source,java]
68+
----
69+
@Bean
70+
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
71+
http
72+
// ...
73+
.csrf(csrf -> csrf.disable()))
74+
return http.build();
75+
}
76+
----
77+
====
78+
79+
[[webflux-csrf-include]]
80+
=== Include the CSRF Token
81+
82+
In order for the <<csrf-protection-stp,synchronizer token pattern>> to protect against CSRF attacks, we must include the actual CSRF token in the HTTP request.
83+
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.
84+
85+
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`.
86+
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-attr,meta tag>>.
87+
88+
[[webflux-csrf-include-subscribe]]
89+
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.
90+
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.
91+
92+
.`CsrfToken` as `@ModelAttribute`
93+
====
94+
[source,java]
95+
----
96+
@ControllerAdvice
97+
public class SecurityControllerAdvice {
98+
@ModelAttribute
99+
Mono<CsrfToken> csrfToken(ServerWebExchange exchange) {
100+
Mono<CsrfToken> csrfToken = exchange.getAttribute(CsrfToken.class.getName());
101+
return csrfToken.doOnSuccess(token -> exchange.getAttributes()
102+
.put(CsrfRequestDataValueProcessor.DEFAULT_CSRF_ATTR_NAME, token));
103+
}
104+
}
105+
----
106+
====
107+
108+
Fortunately, Thymeleaf provides <<webflux-csrf-include-form-auto,integration>> that works without any additional work.
109+
110+
[[webflux-csrf-include-form]]
111+
==== Form URL Encoded
112+
In order to post an HTML form the CSRF token must be included in the form as a hidden input.
113+
For example, the rendered HTML might look like:
114+
115+
.CSRF Token HTML
116+
====
117+
[source,html]
118+
----
119+
<input type="hidden"
120+
name="_csrf"
121+
value="4bfd1575-3ad1-4d21-96c7-4ef2d9f86721"/>
122+
----
123+
====
124+
125+
Next we will discuss various ways of including the CSRF token in a form as a hidden input.
126+
127+
[[webflux-csrf-include-form-auto]]
128+
===== Automatic CSRF Token Inclusion
129+
130+
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].
131+
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].
132+
133+
Fortunately, Thymleaf 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.
134+
135+
[[webflux-csrf-include-form-attr]]
136+
===== CsrfToken Request Attribute
137+
138+
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`.
139+
140+
The Thymeleaf sample below assumes that you <<webflux-csrf-include-subscribe,expose>> the `CsrfToken` on an attribute named `_csrf`.
141+
142+
.CSRF Token in Form with Request Attribute
143+
====
144+
[source,html]
145+
----
146+
<form th:action="@{/logout}"
147+
method="post">
148+
<input type="submit"
149+
value="Log out" />
150+
<input type="hidden"
151+
th:name="${_csrf.parameterName}"
152+
th:value="${_csrf.token}"/>
153+
</form>
154+
----
155+
====
156+
157+
[[webflux-csrf-include-ajax]]
158+
==== Ajax and JSON Requests
159+
If you are using JSON, then it is not possible to submit the CSRF token within an HTTP parameter.
160+
Instead you can submit the token within a HTTP header.
161+
162+
In the following sections we will discuss various ways of including the CSRF token as an HTTP request header in JavaScript based applications.
163+
164+
[[webflux-csrf-include-ajax-auto]]
165+
===== Automatic Inclusion
166+
167+
Spring Security can easily be <<webflux-csrf-configure-custom-repository,configured>> to store the expected CSRF token in a cookie.
168+
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.
169+
170+
[[webflux-csrf-include-ajax-meta]]
171+
===== Meta tags
172+
173+
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.
174+
The HTML might look something like this:
175+
176+
.CSRF meta tag HTML
177+
====
178+
[source,html]
179+
----
180+
<html>
181+
<head>
182+
<meta name="_csrf" content="4bfd1575-3ad1-4d21-96c7-4ef2d9f86721"/>
183+
<meta name="_csrf_header" content="X-CSRF-TOKEN"/>
184+
<!-- ... -->
185+
</head>
186+
<!-- ... -->
187+
----
188+
====
189+
190+
Once the meta tags contained the CSRF token, the JavaScript code would read the meta tags and include the CSRF token as a header.
191+
If you were using jQuery, this could be done with the following:
192+
193+
.AJAX send CSRF Token
194+
====
195+
[source,javascript]
196+
----
197+
$(function () {
198+
var token = $("meta[name='_csrf']").attr("content");
199+
var header = $("meta[name='_csrf_header']").attr("content");
200+
$(document).ajaxSend(function(e, xhr, options) {
201+
xhr.setRequestHeader(header, token);
202+
});
203+
});
204+
----
205+
====
206+
207+
The sample below assumes that you <<webflux-csrf-include-subscribe,expose>> the `CsrfToken` on an attribute named `_csrf`.
208+
An example of doing this with Thymeleaf is shown below:
209+
210+
.CSRF meta tag JSP
211+
====
212+
[source,html]
213+
----
214+
<html>
215+
<head>
216+
<meta name="_csrf" th:content="${_csrf.token}"/>
217+
<!-- default header name is X-CSRF-TOKEN -->
218+
<meta name="_csrf_header" th:content="${_csrf.headerName}"/>
219+
<!-- ... -->
220+
</head>
221+
<!-- ... -->
222+
----
223+
====
224+
225+
[[webflux-csrf-considerations]]
226+
== CSRF Considerations
227+
There are a few special considerations to consider when implementing protection against CSRF attacks.
228+
This section discusses those considerations as it pertains to WebFlux environments.
229+
Refer to <<csrf-considerations>> for a more general discussion.
230+
231+
232+
[[webflux-considerations-csrf-login]]
233+
=== Logging In
234+
235+
It is important to <<csrf-considerations-login,require CSRF for log in>> requests to protect against forging log in attempts.
236+
Spring Security's WebFlux support does this out of the box.
237+
238+
[[webflux-considerations-csrf-logout]]
239+
=== Logging Out
240+
241+
It is important to <<csrf-considerations-logout,require CSRF for log out>> requests to protect against forging log out attempts.
242+
By default Spring Security's `LogoutWebFilter` only processes HTTP post requests.
243+
This ensures that log out requires a CSRF token and that a malicious user cannot forcibly log out your users.
244+
245+
The easiest approach is to use a form to log out.
246+
If you really want a link, you can use JavaScript to have the link perform a POST (i.e. maybe on a hidden form).
247+
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.
248+
249+
If you really want to use HTTP GET with logout you can do so, but remember this is generally not recommended.
250+
For example, the following Java Configuration will perform logout with the URL `/logout` is requested with any HTTP method:
251+
252+
// FIXME: This should be a link to log out documentation
253+
254+
.Log out with HTTP GET
255+
====
256+
[source,java]
257+
----
258+
@Bean
259+
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
260+
http
261+
// ...
262+
.logout(logout -> logout.requiresLogout(new PathPatternParserServerWebExchangeMatcher("/logout")))
263+
return http.build();
264+
}
265+
266+
----
267+
====
268+
269+
270+
[[webflux-considerations-csrf-timeouts]]
271+
=== CSRF and Session Timeouts
272+
273+
By default Spring Security stores the CSRF token in the `WebSession`.
274+
This can lead to a situation where the session expires which means there is not an expected CSRF token to validate against.
275+
276+
We've already discussed <<csrf-considerations-login,general solutions>> to session timeouts.
277+
This section discusses the specifics of CSRF timeouts as it pertains to the WebFlux support.
278+
279+
It is simple to change storage of the expected CSRF token to be in a cookie.
280+
For details, refer to the <<webflux-csrf-configure-custom-repository>> section.
281+
282+
// FIXME: We should add a custom AccessDeniedHandler section in the reference and update the links above
283+
284+
// FIXME: We need a WebFlux multipart body vs action story. WebFlux always has multipart enabled.
285+
[[webflux-csrf-considerations-multipart]]
286+
=== Multipart (file upload)
287+
We have <<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.
288+
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.
289+
290+
[NOTE]
291+
====
292+
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.
293+
====
294+
295+
[[webflux-csrf-considerations-multipart-body]]
296+
==== Place CSRF Token in the Body
297+
298+
We have <<csrf-considerations-multipart,already discussed>> the trade-offs of placing the CSRF token in the body.
299+
300+
In a WebFlux application, this can be configured with the following configuration:
301+
302+
.Enable obtaining CSRF token from multipart/form-data
303+
====
304+
[source,java]
305+
----
306+
@Bean
307+
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
308+
http
309+
// ...
310+
.csrf(csrf -> csrf.tokenFromMultipartDataEnabled(true))
311+
return http.build();
312+
}
313+
314+
----
315+
====
316+
317+
[[webflux-csrf-considerations-multipart-url]]
318+
==== Include CSRF Token in URL
319+
320+
We have <<csrf-considerations-multipart,already discussed>> the trade-offs of placing the CSRF token in the URL.
321+
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.
322+
An example with Thymeleaf is shown below:
323+
324+
.CSRF Token in Action
325+
====
326+
[source,html]
327+
----
328+
<form method="post"
329+
th:action="@{/upload(${_csrf.parameterName}=${_csrf.token})}"
330+
enctype="multipart/form-data">
331+
----
332+
====
333+
334+
[[webflux-csrf-considerations-override-method]]
335+
=== HiddenHttpMethodFilter
336+
We have <<csrf-considerations-override-method,already discussed>> overriding the HTTP method.
337+
338+
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].
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
= Protection Against Exploits
22

3+
include::csrf.adoc[leveloffset=+1]
4+
35
include::headers.adoc[leveloffset=+1]
46

57
include::redirect-https.adoc[leveloffset=+1]

docs/manual/src/docs/asciidoc/_includes/reactive/webflux.adoc

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ You can find a minimal WebFlux Security configuration below:
1515

1616
[source,java]
1717
-----
18+
1819
@EnableWebFluxSecurity
1920
public class HelloWebfluxSecurityConfig {
2021
@@ -38,6 +39,7 @@ You can find an explicit version of the minimal WebFlux Security configuration b
3839

3940
[source,java]
4041
-----
42+
@Configuration
4143
@EnableWebFluxSecurity
4244
public class HelloWebfluxSecurityConfig {
4345

docs/manual/src/docs/asciidoc/_includes/servlet/exploits/csrf.adoc

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
This section discusses Spring Security's <<csrf,Cross Site Request Forgery (CSRF)>> support for servlet environments.
55

6-
[[servelt-csrf-using]]
6+
[[servlet-csrf-using]]
77
== Using Spring Security CSRF Protection
88
The steps to using Spring Security's CSRF protection are outlined below:
99

0 commit comments

Comments
 (0)