Browse Source

Document Reactive CSRF Support

Fixes gh-6487
Rob Winch 5 years ago
parent
commit
2827af15e0

+ 0 - 5
docs/manual/src/docs/asciidoc/_includes/about/exploits/csrf.adoc

@@ -411,8 +411,3 @@ Overriding the HTTP method occurs in a filter.
 That filter must be placed before Spring Security's support.
 Note that overriding only happens on a `post`, so this is actually unlikely to cause any real problems.
 However, it is still best practice to ensure it is placed before Spring Security's filters.
-
-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].
-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.
-
-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].

+ 338 - 0
docs/manual/src/docs/asciidoc/_includes/reactive/exploits/csrf.adoc

@@ -0,0 +1,338 @@
+[[webflux-csrf]]
+= Cross Site Request Forgery (CSRF) for WebFlux Environments
+
+This section discusses Spring Security's <<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 <<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 `CookieCsrfTokenRepository` in Java Configuration using:
+
+.Store CSRF Token in a Cookie with Java Configuration
+====
+[source,java]
+-----
+@Bean
+public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
+	http
+		// ...
+		.csrf(csrf -> csrf.csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse()))
+	return http.build();
+}
+-----
+====
+
+[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 <<csrf-when,makes sense for your application>>.
+
+The Java configuration below will disable CSRF protection.
+
+.Disable CSRF Java Configuration
+====
+[source,java]
+----
+@Bean
+public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
+	http
+		// ...
+		.csrf(csrf -> csrf.disable()))
+	return http.build();
+}
+----
+====
+
+[[webflux-csrf-include]]
+=== Include the CSRF Token
+
+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.
+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-attr,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`
+====
+[source,java]
+----
+@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));
+	}
+}
+----
+====
+
+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, 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.
+
+[[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 <<csrf-considerations>> for a more general discussion.
+
+
+[[webflux-considerations-csrf-login]]
+=== Logging In
+
+It is important to <<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 <<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
+====
+[source,java]
+----
+@Bean
+public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
+	http
+		// ...
+		.logout(logout -> logout.requiresLogout(new PathPatternParserServerWebExchangeMatcher("/logout")))
+	return http.build();
+}
+
+----
+====
+
+
+[[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 <<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 <<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 <<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
+====
+[source,java]
+----
+@Bean
+public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
+	http
+		// ...
+		.csrf(csrf -> csrf.tokenFromMultipartDataEnabled(true))
+	return http.build();
+}
+
+----
+====
+
+[[webflux-csrf-considerations-multipart-url]]
+==== Include CSRF Token in URL
+
+We have <<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 <<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].

+ 2 - 0
docs/manual/src/docs/asciidoc/_includes/reactive/exploits/index.adoc

@@ -1,5 +1,7 @@
 = Protection Against Exploits
 
+include::csrf.adoc[leveloffset=+1]
+
 include::headers.adoc[leveloffset=+1]
 
 include::redirect-https.adoc[leveloffset=+1]

+ 2 - 0
docs/manual/src/docs/asciidoc/_includes/reactive/webflux.adoc

@@ -15,6 +15,7 @@ You can find a minimal WebFlux Security configuration below:
 
 [source,java]
 -----
+
 @EnableWebFluxSecurity
 public class HelloWebfluxSecurityConfig {
 
@@ -38,6 +39,7 @@ You can find an explicit version of the minimal WebFlux Security configuration b
 
 [source,java]
 -----
+@Configuration
 @EnableWebFluxSecurity
 public class HelloWebfluxSecurityConfig {
 

+ 1 - 1
docs/manual/src/docs/asciidoc/_includes/servlet/exploits/csrf.adoc

@@ -3,7 +3,7 @@
 
 This section discusses Spring Security's <<csrf,Cross Site Request Forgery (CSRF)>> support for servlet environments.
 
-[[servelt-csrf-using]]
+[[servlet-csrf-using]]
 == Using Spring Security CSRF Protection
 The steps to using Spring Security's CSRF protection are outlined below: