|
@@ -7,8 +7,20 @@ The following steps relate to changes around how to configure CSRF.
|
|
|
In Spring Security 5, the default behavior is that the `CsrfToken` will be loaded on every request.
|
|
|
This means that in a typical setup, the `HttpSession` must be read for every request even if it is unnecessary.
|
|
|
|
|
|
+[NOTE]
|
|
|
+====
|
|
|
+Some examples of where it should be unnecessary to read the session include endpoints marked `permitAll()` such as static assets, static HTML pages, single-page applications hosted under the same domain/server, etc.
|
|
|
+====
|
|
|
+
|
|
|
In Spring Security 6, the default is that the lookup of the `CsrfToken` will be deferred until it is needed.
|
|
|
|
|
|
+[NOTE]
|
|
|
+====
|
|
|
+The `CsrfToken` is needed whenever a request is made with an HTTP verb that would change the state of the application.
|
|
|
+This is covered in detail in xref:features/exploits/csrf.adoc#csrf-protection-idempotent[Safe Methods Must be Idempotent].
|
|
|
+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.
|
|
|
+====
|
|
|
+
|
|
|
To opt into the new Spring Security 6 default, the following configuration can be used.
|
|
|
|
|
|
[[servlet-opt-in-defer-loading-csrf-token]]
|
|
@@ -18,7 +30,7 @@ To opt into the new Spring Security 6 default, the following configuration can b
|
|
|
[source,java,role="primary"]
|
|
|
----
|
|
|
@Bean
|
|
|
-DefaultSecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
|
|
|
+public SecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
|
|
|
CsrfTokenRequestAttributeHandler requestHandler = new CsrfTokenRequestAttributeHandler();
|
|
|
// set the name of the attribute the CsrfToken will be populated on
|
|
|
requestHandler.setCsrfRequestAttributeName("_csrf");
|
|
@@ -61,7 +73,156 @@ open fun springSecurity(http: HttpSecurity): SecurityFilterChain {
|
|
|
----
|
|
|
====
|
|
|
|
|
|
-If this breaks your application, then you can explicitly opt into the 5.8 defaults using the following configuration:
|
|
|
+[NOTE]
|
|
|
+====
|
|
|
+When the `CsrfToken` is deferred (the default in Spring Security 6), some applications may break due to the fact that they were designed with non-deferred CSRF tokens.
|
|
|
+See <<servlet-defer-loading-csrf-token-opt-out,Opt-out Steps>> below for more information.
|
|
|
+====
|
|
|
+
|
|
|
+[[servlet-defer-loading-csrf-token-opt-out]]
|
|
|
+=== Opt-out Steps
|
|
|
+
|
|
|
+If configuring the `CsrfToken` to be deferred gives you trouble, take a look at these scenarios for optimal opt out behavior:
|
|
|
+
|
|
|
+==== I am using a Single-Page Application with `CookieCsrfTokenRepository`
|
|
|
+
|
|
|
+If you are using a single-page app (SPA) to connect to a backend protected by Spring Security along with `CookieCsrfTokenRepository.withHttpOnlyFalse()`, you may find that the CSRF token is no longer returned to your application as a cookie on the first request to the server.
|
|
|
+
|
|
|
+In this case, you have several options for restoring the behavior your client-side application expects.
|
|
|
+One option is to add a `Filter` that eagerly renders the `CsrfToken` to the response regardless of which request is made first, like so:
|
|
|
+
|
|
|
+.Add a `Filter` to return a cookie on the response
|
|
|
+====
|
|
|
+.Java
|
|
|
+[source,java,role="primary"]
|
|
|
+----
|
|
|
+@Bean
|
|
|
+public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
|
|
+ CookieCsrfTokenRepository tokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse();
|
|
|
+ CsrfTokenRequestAttributeHandler requestHandler = new CsrfTokenRequestAttributeHandler();
|
|
|
+ // set the name of the attribute the CsrfToken will be populated on
|
|
|
+ requestHandler.setCsrfRequestAttributeName("_csrf");
|
|
|
+ http
|
|
|
+ // ...
|
|
|
+ .csrf((csrf) -> csrf
|
|
|
+ .csrfTokenRepository(tokenRepository)
|
|
|
+ .csrfTokenRequestHandler(requestHandler)
|
|
|
+ )
|
|
|
+ .addFilterAfter(new CsrfCookieFilter(), BasicAuthenticationFilter.class);
|
|
|
+
|
|
|
+ return http.build();
|
|
|
+}
|
|
|
+
|
|
|
+private static final class CsrfCookieFilter extends OncePerRequestFilter {
|
|
|
+
|
|
|
+ @Override
|
|
|
+ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
|
|
+ throws ServletException, IOException {
|
|
|
+ CsrfToken csrfToken = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
|
|
|
+ // Render the token value to a cookie by causing the deferred token to be loaded
|
|
|
+ csrfToken.getToken();
|
|
|
+
|
|
|
+ filterChain.doFilter(request, response);
|
|
|
+ }
|
|
|
+
|
|
|
+}
|
|
|
+----
|
|
|
+
|
|
|
+.Kotlin
|
|
|
+[source,kotlin,role="secondary"]
|
|
|
+----
|
|
|
+@Bean
|
|
|
+open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
|
|
|
+ val tokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse()
|
|
|
+ val requestHandler = CsrfTokenRequestAttributeHandler()
|
|
|
+ // set the name of the attribute the CsrfToken will be populated on
|
|
|
+ requestHandler.setCsrfRequestAttributeName("_csrf")
|
|
|
+ http {
|
|
|
+ csrf {
|
|
|
+ csrfTokenRepository = tokenRepository
|
|
|
+ csrfTokenRequestHandler = requestHandler
|
|
|
+ }
|
|
|
+ addFilterAfter<BasicAuthenticationFilter>(CsrfCookieFilter())
|
|
|
+ }
|
|
|
+ return http.build()
|
|
|
+}
|
|
|
+
|
|
|
+class CsrfCookieFilter : OncePerRequestFilter() {
|
|
|
+
|
|
|
+ override fun doFilterInternal(request: HttpServletRequest, response: HttpServletResponse, filterChain: FilterChain) {
|
|
|
+ val csrfToken = request.getAttribute(CsrfToken::class.java.name) as CsrfToken
|
|
|
+ // Render the token value to a cookie by causing the deferred token to be loaded
|
|
|
+ csrfToken.token
|
|
|
+
|
|
|
+ filterChain.doFilter(request, response)
|
|
|
+ }
|
|
|
+
|
|
|
+}
|
|
|
+----
|
|
|
+====
|
|
|
+
|
|
|
+The option above does not require changes to the single-page application, but does cause the `CsrfToken` to be loaded on every request.
|
|
|
+If you do not wish to add a `Filter` to eagerly load tokens on every request, additional options are listed below.
|
|
|
+
|
|
|
+==== I am using a Single-Page Application with `HttpSessionCsrfTokenRepository`
|
|
|
+
|
|
|
+If you are using sessions, your application will benefit from deferred tokens.
|
|
|
+Instead of opting out, another option is to add a new `@RestController` with a `/csrf` endpoint, like so:
|
|
|
+
|
|
|
+.Add a `/csrf` endpoint
|
|
|
+====
|
|
|
+.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.
|
|
|
+====
|
|
|
+
|
|
|
+The `/csrf` endpoint would need to be consumed by the client-side application in order to bootstrap the application for subsequent requests.
|
|
|
+
|
|
|
+[NOTE]
|
|
|
+====
|
|
|
+Instructions for calling the `/csrf` endpoint on application launch are specific to your client-side framework and therefore outside the scope of this document.
|
|
|
+====
|
|
|
+
|
|
|
+[NOTE]
|
|
|
+====
|
|
|
+While this requires changes to your single-page application, the benefit is that the CSRF token is only loaded once and the token can continue to be deferred.
|
|
|
+This approach works particularly well with applications that use `HttpSessionCsrfTokenRepository` and do benefit from deferred tokens by allowing the `HttpSession` not to be read on every request.
|
|
|
+====
|
|
|
+
|
|
|
+If you simply wish to opt out of deferred tokens altogether, that option is listed next.
|
|
|
+
|
|
|
+==== I need to opt out of deferred tokens for another reason
|
|
|
+
|
|
|
+If deferred tokens break your application for another reason, then you can explicitly opt into the 5.8 defaults using the following configuration:
|
|
|
|
|
|
.Explicit Configure `CsrfToken` with 5.8 Defaults
|
|
|
====
|
|
@@ -69,7 +230,7 @@ If this breaks your application, then you can explicitly opt into the 5.8 defaul
|
|
|
[source,java,role="primary"]
|
|
|
----
|
|
|
@Bean
|
|
|
-DefaultSecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
|
|
|
+public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
|
|
CsrfTokenRequestAttributeHandler requestHandler = new CsrfTokenRequestAttributeHandler();
|
|
|
// set the name of the attribute the CsrfToken will be populated on
|
|
|
requestHandler.setCsrfRequestAttributeName(null);
|
|
@@ -86,7 +247,7 @@ DefaultSecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
|
|
|
[source,kotlin,role="secondary"]
|
|
|
----
|
|
|
@Bean
|
|
|
-open fun springSecurity(http: HttpSecurity): SecurityFilterChain {
|
|
|
+open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
|
|
|
val requestHandler = CsrfTokenRequestAttributeHandler()
|
|
|
// set the name of the attribute the CsrfToken will be populated on
|
|
|
requestHandler.setCsrfRequestAttributeName(null)
|
|
@@ -115,6 +276,12 @@ open fun springSecurity(http: HttpSecurity): SecurityFilterChain {
|
|
|
----
|
|
|
====
|
|
|
|
|
|
+[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.
|
|
|
+====
|
|
|
+
|
|
|
== Protect against CSRF BREACH
|
|
|
|
|
|
If the steps for <<Defer Loading CsrfToken>> work for you, then you can also opt into Spring Security 6's default support for BREACH protection of the `CsrfToken` using the following configuration:
|
|
@@ -240,6 +407,21 @@ open fun springSecurity(http: HttpSecurity): SecurityFilterChain {
|
|
|
----
|
|
|
====
|
|
|
|
|
|
+This is the RECOMMENDED way to configure Spring Security to work with a client-side application that uses cookie values, because it continues to allow the response to return a randomized value for the CSRF token in case the application returns HTML or other responses that could be vulnerable to BREACH without your knowledge.
|
|
|
+
|
|
|
+[NOTE]
|
|
|
+====
|
|
|
+BREACH protection works to protect the token when it is included in a response body that can be GZIP compressed, which generally does not include headers and cookies.
|
|
|
+====
|
|
|
+
|
|
|
+[TIP]
|
|
|
+====
|
|
|
+Any token value returned by the server can be used successfully by the client-side application because the underlying (raw) CSRF token does not change.
|
|
|
+It is not required for an AngularJS (or similar) application to refresh the CSRF token before/after every request.
|
|
|
+====
|
|
|
+
|
|
|
+If you simply wish to opt out of CSRF BREACH protection altogether, that option is listed next.
|
|
|
+
|
|
|
==== I need to opt out of CSRF BREACH protection for another reason
|
|
|
|
|
|
If CSRF BREACH protection does not work for you for another reason, you can opt out using the configuration from the <<servlet-opt-in-defer-loading-csrf-token>> section.
|