diff --git a/docs/modules/ROOT/assets/images/servlet/exploits/csrf-processing.odg b/docs/modules/ROOT/assets/images/servlet/exploits/csrf-processing.odg new file mode 100644 index 0000000000..251753f160 Binary files /dev/null and b/docs/modules/ROOT/assets/images/servlet/exploits/csrf-processing.odg differ diff --git a/docs/modules/ROOT/assets/images/servlet/exploits/csrf-processing.png b/docs/modules/ROOT/assets/images/servlet/exploits/csrf-processing.png new file mode 100644 index 0000000000..bf77a6e650 Binary files /dev/null and b/docs/modules/ROOT/assets/images/servlet/exploits/csrf-processing.png differ diff --git a/docs/modules/ROOT/assets/images/servlet/exploits/csrf.odg b/docs/modules/ROOT/assets/images/servlet/exploits/csrf.odg new file mode 100644 index 0000000000..08e24eb9ae Binary files /dev/null and b/docs/modules/ROOT/assets/images/servlet/exploits/csrf.odg differ diff --git a/docs/modules/ROOT/assets/images/servlet/exploits/csrf.png b/docs/modules/ROOT/assets/images/servlet/exploits/csrf.png new file mode 100644 index 0000000000..b13c7f17c8 Binary files /dev/null and b/docs/modules/ROOT/assets/images/servlet/exploits/csrf.png differ diff --git a/docs/modules/ROOT/pages/servlet/authentication/logout.adoc b/docs/modules/ROOT/pages/servlet/authentication/logout.adoc index f539123ba7..7adaddc651 100644 --- a/docs/modules/ROOT/pages/servlet/authentication/logout.adoc +++ b/docs/modules/ROOT/pages/servlet/authentication/logout.adoc @@ -403,6 +403,6 @@ Once you have logout configured you can test it using xref:servlet/test/mockmvc/ - xref:servlet/test/mockmvc/logout.adoc#test-logout[Testing Logout] - xref:servlet/integrations/servlet-api.adoc#servletapi-logout[HttpServletRequest.logout()] - xref:servlet/authentication/rememberme.adoc#remember-me-impls[Remember-Me Interfaces and Implementations] -- xref:servlet/exploits/csrf.adoc#servlet-considerations-csrf-logout[Logging Out] in section CSRF Caveats +- xref:servlet/exploits/csrf.adoc#csrf-considerations-logout[Logging Out] in section CSRF Caveats - Section xref:servlet/authentication/cas.adoc#cas-singlelogout[Single Logout] (CAS protocol) - Documentation for the xref:servlet/appendix/namespace/http.adoc#nsa-logout[logout element] in the Spring Security XML Namespace section diff --git a/docs/modules/ROOT/pages/servlet/authentication/passwords/form.adoc b/docs/modules/ROOT/pages/servlet/authentication/passwords/form.adoc index 95c00337f8..5254459067 100644 --- a/docs/modules/ROOT/pages/servlet/authentication/passwords/form.adoc +++ b/docs/modules/ROOT/pages/servlet/authentication/passwords/form.adoc @@ -183,7 +183,7 @@ The following https://www.thymeleaf.org/[Thymeleaf] template produces an HTML lo There are a few key points about the default HTML form: * The form should perform a `post` to `/login`. -* The form needs to include a xref:servlet/exploits/csrf.adoc#servlet-csrf[CSRF Token], which is xref:servlet/exploits/csrf.adoc#servlet-csrf-include-form-auto[automatically included] by Thymeleaf. +* The form needs to include a xref:servlet/exploits/csrf.adoc#servlet-csrf[CSRF Token], which is xref:servlet/exploits/csrf.adoc#csrf-integration-form[automatically included] by Thymeleaf. * The form should specify the username in a parameter named `username`. * The form should specify the password in a parameter named `password`. * If the HTTP parameter named `error` is found, it indicates the user failed to provide a valid username or password. diff --git a/docs/modules/ROOT/pages/servlet/exploits/csrf.adoc b/docs/modules/ROOT/pages/servlet/exploits/csrf.adoc index c6bfd2feac..60622f2e4b 100644 --- a/docs/modules/ROOT/pages/servlet/exploits/csrf.adoc +++ b/docs/modules/ROOT/pages/servlet/exploits/csrf.adoc @@ -1,43 +1,252 @@ [[servlet-csrf]] -= Cross Site Request Forgery (CSRF) for Servlet Environments += Cross Site Request Forgery (CSRF) +:figures: servlet/exploits -This section discusses Spring Security's xref:features/exploits/csrf.adoc#csrf[Cross Site Request Forgery (CSRF)] support for servlet environments. +In an application where end users can xref:servlet/authentication/index.adoc[log in], it is important to consider how to protect against xref:features/exploits/csrf.adoc#csrf[Cross Site Request Forgery (CSRF)]. -[[servlet-csrf-using]] -== Using Spring Security CSRF Protection -The steps to using Spring Security's CSRF protection are outlined below: +Spring Security protects against CSRF attacks by default for xref:features/exploits/csrf.adoc#csrf-protection-idempotent[unsafe HTTP methods], such as a POST request, so no additional code is necessary. +You can specify the default configuration explicitly using the following: -* <> -* <> -* <> - -[[servlet-csrf-idempotent]] -=== Use proper HTTP verbs -The first step to protecting against CSRF attacks is to ensure that 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]. - -[[servlet-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. -The next few sections cover a few common customizations. - -[[servlet-csrf-configure-custom-repository]] -==== Custom CsrfTokenRepository - -By default, Spring Security stores the expected CSRF token in the `HttpSession` by using `HttpSessionCsrfTokenRepository`. -There can be cases where users want to configure a custom `CsrfTokenRepository`. -For example, it might be desirable to persist the `CsrfToken` in a cookie to <>. - -By default, the `CookieCsrfTokenRepository` writes to a cookie named `XSRF-TOKEN` and reads 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 XML byusing the following: - - -.Store CSRF Token in a Cookie with XML Configuration +[[csrf-configuration]] +.Configure CSRF Protection ==== -[source,xml] +.Java +[source,java,role="primary"] +---- +@Configuration +@EnableWebSecurity +public class SecurityConfig { + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http + // ... + .csrf(Customizer.withDefaults()); + return http.build(); + } +} +---- + +.Kotlin +[source,kotlin,role="secondary"] +---- +import org.springframework.security.config.annotation.web.invoke + +@Configuration +@EnableWebSecurity +class SecurityConfig { + + @Bean + open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { + http { + // ... + csrf { } + } + return http.build() + } +} +---- + +.XML +[source,xml,role="secondary"] +---- + + + + +---- +==== + +To learn more about CSRF protection for your application, consider the following use cases: + +* I want to <> +* I need to <> +* I want to <> instead of <> +* I want to <> +* I want to <> +* I want to <> +* I need guidance integrating <> with the backend +* I need guidance integrating <> with the backend +* I need guidance integrating <> with the backend +* I need guidance on <> +* I want to <> +* I need guidance on <> + +[[csrf-components]] +== Understanding CSRF Protection's Components + +CSRF protection is provided by several components that are composed within the {security-api-url}org/springframework/security/web/csrf/CsrfFilter.html[`CsrfFilter`]: + +.`CsrfFilter` Components +image::{figures}/csrf.png[] + +CSRF protection is divided into two parts: + +1. Make the {security-api-url}org/springframework/security/web/csrf/CsrfToken.html[`CsrfToken`] available to the application by delegating to the <>. +2. Determine if the request requires CSRF protection, load and validate the token, and <>. + +.`CsrfFilter` Processing +image::{figures}/csrf-processing.png[] + +* image:{icondir}/number_1.png[] First, the {security-api-url}org/springframework/security/web/csrf/DeferredCsrfToken.html[`DeferredCsrfToken`] is loaded, which holds a reference to the <> so that the persisted `CsrfToken` can be loaded later (in image:{icondir}/number_4.png[]). +* image:{icondir}/number_2.png[] Second, a `Supplier` (created from `DeferredCsrfToken`) is given to the <>, which is responsible for populating a request attribute to make the `CsrfToken` available to the rest of the application. +* image:{icondir}/number_3.png[] Next, the main CSRF protection processing begins and checks if the current request requires CSRF protection. If not required, the filter chain is continued and processing ends. +* image:{icondir}/number_4.png[] If CSRF protection is required, the persisted `CsrfToken` is finally loaded from the `DeferredCsrfToken`. +* image:{icondir}/number_5.png[] Continuing, the actual CSRF token provided by the client (if any) is resolved using the <>. +* image:{icondir}/number_6.png[] The actual CSRF token is compared against the persisted `CsrfToken`. If valid, the filter chain is continued and processing ends. +* image:{icondir}/number_7.png[] If the actual CSRF token is invalid (or missing), an `AccessDeniedException` is passed to the <> and processing ends. + +[[migrating-to-spring-security-6]] +== Migrating to Spring Security 6 + +When migrating from Spring Security 5 to 6, there are a few changes that may impact your application. +The following is an overview of the aspects of CSRF protection that have changed in Spring Security 6: + +* Loading of the `CsrfToken` is now <> to improve performance by no longer requiring the session to be loaded on every request. +* The `CsrfToken` now includes <> to protect the CSRF token from a https://en.wikipedia.org/wiki/BREACH[BREACH] attack. + +[TIP] +==== +The changes in Spring Security 6 require additional configuration for single-page applications, and as such you may find the <> section particularly useful. +==== + +See the xref:migration/servlet/exploits.adoc[Exploit Protection] section of the xref:migration/servlet/index.adoc[Migration] chapter for more information on migrating a Spring Security 5 application. + +[[csrf-token-repository]] +== Persisting the `CsrfToken` + +The `CsrfToken` is persisted using a `CsrfTokenRepository`. + +By default, the <> is used for storing tokens in a session. +Spring Security also provides the <> for storing tokens in a cookie. +You can also specify <> to store tokens wherever you like. + +[[csrf-token-repository-httpsession]] +=== Using the `HttpSessionCsrfTokenRepository` + +By default, Spring Security stores the expected CSRF token in the `HttpSession` by using {security-api-url}org/springframework/security/web/csrf/HttpSessionCsrfTokenRepository.html[`HttpSessionCsrfTokenRepository`], so no additional code is necessary. + +The `HttpSessionCsrfTokenRepository` reads the token from an HTTP request header named `X-CSRF-TOKEN` or the request parameter `_csrf` by default. + +You can specify the default configuration explicitly using the following configuration: + +[[csrf-token-repository-httpsession-configuration]] +.Configure `HttpSessionCsrfTokenRepository` +==== +.Java +[source,java,role="primary"] +---- +@Configuration +@EnableWebSecurity +public class SecurityConfig { + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http + // ... + .csrf((csrf) -> csrf + .csrfTokenRepository(new HttpSessionCsrfTokenRepository()) + ); + return http.build(); + } +} +---- + +.Kotlin +[source,kotlin,role="secondary"] +---- +import org.springframework.security.config.annotation.web.invoke + +@Configuration +@EnableWebSecurity +class SecurityConfig { + + @Bean + open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { + http { + // ... + csrf { + csrfTokenRepository = HttpSessionCsrfTokenRepository() + } + } + return http.build() + } +} +---- + +.XML +[source,xml,role="secondary"] +---- + + + + + +---- +==== + +[[csrf-token-repository-cookie]] +=== Using the `CookieCsrfTokenRepository` + +You can persist the `CsrfToken` in a cookie to <> using the {security-api-url}org/springframework/security/web/csrf/CookieCsrfTokenRepository.html[`CookieCsrfTokenRepository`]. + +The `CookieCsrfTokenRepository` writes to a cookie named `XSRF-TOKEN` and reads it from an HTTP request header named `X-XSRF-TOKEN` or the request parameter `_csrf` by default. +These defaults come from Angular and its predecessor https://docs.angularjs.org/api/ng/service/$http#cross-site-request-forgery-xsrf-protection[AngularJS]. + +[TIP] +==== +See the https://angular.io/guide/http-security-xsrf-protection[Cross-Site Request Forgery (XSRF) protection] guide and the https://angular.io/api/common/http/HttpClientXsrfModule[HttpClientXsrfModule] for more recent information on this topic. +==== + +You can configure the `CookieCsrfTokenRepository` using the following configuration: + +[[csrf-token-repository-cookie-configuration]] +.Configure `CookieCsrfTokenRepository` +==== +.Java +[source,java,role="primary"] +---- +@Configuration +@EnableWebSecurity +public class SecurityConfig { + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http + // ... + .csrf((csrf) -> csrf + .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) + ); + return http.build(); + } +} +---- + +.Kotlin +[source,kotlin,role="secondary"] +---- +import org.springframework.security.config.annotation.web.invoke + +@Configuration +@EnableWebSecurity +class SecurityConfig { + + @Bean + open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { + http { + // ... + csrf { + csrfTokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse() + } + } + return http.build() + } +} +---- + +.XML +[source,xml,role="secondary"] ---- @@ -51,28 +260,34 @@ You can configure `CookieCsrfTokenRepository` in XML byusing the following: [NOTE] ==== -The sample explicitly sets `cookieHttpOnly=false`. -This is necessary to allow JavaScript (such as AngularJS) to read it. -If you do not need the ability to read the cookie with JavaScript directly, we recommend omitting `cookieHttpOnly=false` to improve security. +The example explicitly sets `HttpOnly` to `false`. +This is necessary to let JavaScript frameworks (such as Angular) read it. +If you do not need the ability to read the cookie with JavaScript directly, we _recommend_ omitting `HttpOnly` (by using `new CookieCsrfTokenRepository()` instead) to improve security. ==== +[[csrf-token-repository-custom]] +=== Customizing the `CsrfTokenRepository` -You can configure `CookieCsrfTokenRepository` in Java or Kotlin configuration by using: +There can be cases where you want to implement a custom {security-api-url}org/springframework/security/web/csrf/CsrfTokenRepository.html[`CsrfTokenRepository`]. -.Store CSRF Token in a Cookie +Once you've implemented the `CsrfTokenRepository` interface, you can configure Spring Security to use it with the following configuration: + +[[csrf-token-repository-custom-configuration]] +.Configure Custom `CsrfTokenRepository` ==== .Java [source,java,role="primary"] ---- @Configuration @EnableWebSecurity -public class WebSecurityConfig { +public class SecurityConfig { @Bean - public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http - .csrf(csrf -> csrf - .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) + // ... + .csrf((csrf) -> csrf + .csrfTokenRepository(new CustomCsrfTokenRepository()) ); return http.build(); } @@ -82,63 +297,86 @@ public class WebSecurityConfig { .Kotlin [source,kotlin,role="secondary"] ---- +import org.springframework.security.config.annotation.web.invoke + @Configuration @EnableWebSecurity class SecurityConfig { @Bean - open fun filterChain(http: HttpSecurity): SecurityFilterChain { - http { + open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { + http { + // ... csrf { - csrfTokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse() + csrfTokenRepository = CustomCsrfTokenRepository() } } return http.build() } } ---- -==== -[NOTE] -==== -The sample explicitly sets `cookieHttpOnly=false`. -This is necessary to let JavaScript (such as AngularJS) read it. -If you do not need the ability to read the cookie with JavaScript directly, we recommend omitting `cookieHttpOnly=false` (by using `new CookieCsrfTokenRepository()` instead) to improve security. -==== - -[[servlet-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 following XML configuration disables CSRF protection: - -.Disable CSRF XML Configuration -==== -[source,xml] +.XML +[source,xml,role="secondary"] ---- - + + ---- ==== -The following Java or Kotlin configuration disables CSRF protection: +[[csrf-token-request-handler]] +== Handling the `CsrfToken` -.Disable CSRF +The `CsrfToken` is made available to an application using a `CsrfTokenRequestHandler`. +This component is also responsible for resolving the `CsrfToken` from HTTP headers or request parameters. + +By default, the <> is used for providing https://en.wikipedia.org/wiki/BREACH[BREACH] protection of the `CsrfToken`. +Spring Security also provides the <> for opting out of BREACH protection. +You can also specify <> to customize the strategy for handling and resolving tokens. + +[[csrf-token-request-handler-breach]] +=== Using the `XorCsrfTokenRequestAttributeHandler` (BREACH) + +The `XorCsrfTokenRequestAttributeHandler` makes the `CsrfToken` available as an `HttpServletRequest` attribute called `_csrf`, and additionally provides protection for https://en.wikipedia.org/wiki/BREACH[BREACH]. + +[NOTE] +==== +The `CsrfToken` is also made available as a request attribute using the name `CsrfToken.class.getName()`. +This name is not configurable, but the name `_csrf` can be changed using `XorCsrfTokenRequestAttributeHandler#setCsrfRequestAttributeName`. +==== + +This implementation also resolves the token value from the request as either a request header (one of <> or <> by default) or a request parameter (`_csrf` by default). + +[NOTE] +==== +BREACH protection is provided by encoding randomness into the CSRF token value to ensure the returned `CsrfToken` changes on every request. +When the token is later resolved as a header value or request parameter, it is decoded to obtain the raw token which is then compared to the <>. +==== + +Spring Security protects the CSRF token from a BREACH attack by default, so no additional code is necessary. +You can specify the default configuration explicitly using the following configuration: + +[[csrf-token-request-handler-breach-configuration]] +.Configure BREACH protection ==== .Java [source,java,role="primary"] ---- @Configuration @EnableWebSecurity -public class WebSecurityConfig { +public class SecurityConfig { @Bean - public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http - .csrf(csrf -> csrf.disable()); + // ... + .csrf((csrf) -> csrf + .csrfTokenRequestHandler(new XorCsrfTokenRequestAttributeHandler()) + ); return http.build(); } } @@ -147,60 +385,67 @@ public class WebSecurityConfig { .Kotlin [source,kotlin,role="secondary"] ---- +import org.springframework.security.config.annotation.web.invoke + @Configuration @EnableWebSecurity class SecurityConfig { @Bean - open fun filterChain(http: HttpSecurity): SecurityFilterChain { - http { + open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { + http { + // ... csrf { - disable() + csrfTokenRequestHandler = XorCsrfTokenRequestAttributeHandler() } } return http.build() } } ---- -==== -[[servlet-csrf-configure-request-handler]] -==== Configure CsrfTokenRequestHandler - -Spring Security's https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/web/csrf/CsrfFilter.html[`CsrfFilter`] exposes a https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/web/csrf/CsrfToken.html[`CsrfToken`] as an `HttpServletRequest` attribute named `_csrf` with the help of a https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/web/csrf/CsrfTokenRequestHandler.html[CsrfTokenRequestHandler]. -In 5.8, the default implementation was `CsrfTokenRequestAttributeHandler` which simply makes the `_csrf` attribute available as a request attribute. - -As of 6.0, the default implementation is `XorCsrfTokenRequestAttributeHandler`, 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 `CsrfTokenRequestAttributeHandler` in XML using the following: - -.Disable BREACH protection XML Configuration -==== -[source,xml] +.XML +[source,xml,role="secondary"] ---- + class="org.springframework.security.web.csrf.XorCsrfTokenRequestAttributeHandler"/> ---- ==== -You can configure `CsrfTokenRequestAttributeHandler` in Java Configuration using the following: +[[csrf-token-request-handler-plain]] +=== Using the `CsrfTokenRequestAttributeHandler` -.Disable BREACH protection +The `CsrfTokenRequestAttributeHandler` makes the `CsrfToken` available as an `HttpServletRequest` attribute called `_csrf`. + +[NOTE] +==== +The `CsrfToken` is also made available as a request attribute using the name `CsrfToken.class.getName()`. +This name is not configurable, but the name `_csrf` can be changed using `CsrfTokenRequestAttributeHandler#setCsrfRequestAttributeName`. +==== + +This implementation also resolves the token value from the request as either a request header (one of <> or <> by default) or a request parameter (`_csrf` by default). + +[[csrf-token-request-handler-opt-out-of-breach]] +The primary use of `CsrfTokenRequestAttributeHandler` is to opt-out of BREACH protection of the `CsrfToken`, which can be configured using the following configuration: + +.Opt-out of BREACH protection ==== .Java [source,java,role="primary"] ---- +@Configuration @EnableWebSecurity -public class WebSecurityConfig { +public class SecurityConfig { @Bean - public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http - .csrf(csrf -> csrf + // ... + .csrf((csrf) -> csrf .csrfTokenRequestHandler(new CsrfTokenRequestAttributeHandler()) ); return http.build(); @@ -211,12 +456,16 @@ public class WebSecurityConfig { .Kotlin [source,kotlin,role="secondary"] ---- +import org.springframework.security.config.annotation.web.invoke + +@Configuration @EnableWebSecurity class SecurityConfig { @Bean - open fun filterChain(http: HttpSecurity): SecurityFilterChain { - http { + open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { + http { + // ... csrf { csrfTokenRequestHandler = CsrfTokenRequestAttributeHandler() } @@ -225,24 +474,197 @@ class SecurityConfig { } } ---- + +.XML +[source,xml,role="secondary"] +---- + + + + + +---- ==== -[[servlet-csrf-include]] -=== Include the CSRF Token +[[csrf-token-request-handler-custom]] +=== Customizing the `CsrfTokenRequestHandler` + +You can implement the `CsrfTokenRequestHandler` interface to customize the strategy for handling and resolving tokens. + +[TIP] +==== +The `CsrfTokenRequestHandler` interface is a `@FunctionalInterface` that can be implemented using a lambda expression to customize request handling. +You will need to implement the full interface to customize how tokens are resolved from the request. +See <> for an example that uses delegation to implement a custom strategy for handling and resolving tokens. +==== + +Once you've implemented the `CsrfTokenRequestHandler` interface, you can configure Spring Security to use it with the following configuration: + +[[csrf-token-request-handler-custom-configuration]] +.Configure Custom `CsrfTokenRequestHandler` +==== +.Java +[source,java,role="primary"] +---- +@Configuration +@EnableWebSecurity +public class SecurityConfig { + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http + // ... + .csrf((csrf) -> csrf + .csrfTokenRequestHandler(new CustomCsrfTokenRequestHandler()) + ); + return http.build(); + } +} +---- + +.Kotlin +[source,kotlin,role="secondary"] +---- +import org.springframework.security.config.annotation.web.invoke + +@Configuration +@EnableWebSecurity +class SecurityConfig { + + @Bean + open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { + http { + // ... + csrf { + csrfTokenRequestHandler = CustomCsrfTokenRequestHandler() + } + } + return http.build() + } +} +---- + +.XML +[source,xml,role="secondary"] +---- + + + + + +---- +==== + +[[deferred-csrf-token]] +== Deferred Loading of the `CsrfToken` + +By default, Spring Security defers loading of the `CsrfToken` until it is needed. + +[NOTE] +==== +The `CsrfToken` is needed whenever a request is made with an xref:features/exploits/csrf.adoc#csrf-protection-idempotent[unsafe HTTP method], such as a POST. +Additionally, it is needed by any request that renders the token to the response, such as a web page with a `
` tag that includes a hidden `` for the CSRF token. +==== + +Because Spring Security also stores the `CsrfToken` in the `HttpSession` by default, deferred CSRF tokens can improve performance by not requiring the session to be loaded on every request. + +[[deferred-csrf-token-opt-out]] +In the event that you want to opt-out of deferred tokens and cause the `CsrfToken` to be loaded on every request, you can do so with the following configuration: + +[[deferred-csrf-token-opt-out-configuration]] +.Opt-out of Deferred CSRF Tokens +==== +.Java +[source,java,role="primary"] +---- +@Configuration +@EnableWebSecurity +public class SecurityConfig { + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + XorCsrfTokenRequestAttributeHandler requestHandler = new XorCsrfTokenRequestAttributeHandler(); + // set the name of the attribute the CsrfToken will be populated on + requestHandler.setCsrfRequestAttributeName(null); + http + // ... + .csrf((csrf) -> csrf + .csrfTokenRequestHandler(requestHandler) + ); + return http.build(); + } +} +---- + +.Kotlin +[source,kotlin,role="secondary"] +---- +import org.springframework.security.config.annotation.web.invoke + +@Configuration +@EnableWebSecurity +class SecurityConfig { + + @Bean + open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { + val requestHandler = XorCsrfTokenRequestAttributeHandler() + // set the name of the attribute the CsrfToken will be populated on + requestHandler.setCsrfRequestAttributeName(null) + http { + // ... + csrf { + csrfTokenRequestHandler = requestHandler + } + } + return http.build() + } +} +---- + +.XML +[source,xml,role="secondary"] +---- + + + + + + + + + +---- +==== + +[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. +==== + + +[[csrf-integration]] +== Integrating with CSRF Protection 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 (a form parameter, an HTTP header, or other part) that is not automatically included in the HTTP request by the browser. -<> that the `CsrfToken` is exposed as a request attribute. -This means that any view technology can access the `CsrfToken` to expose the expected token as either a <> or <>. -Fortunately, there are integrations listed later in this chapter that make including the token in <> and <> requests even easier. +The following sections describe the various ways a frontend or client application can integrate with a CSRF-protected backend application: -[[servlet-csrf-include-form]] -==== Form URL Encoded -To post an HTML form, the CSRF token must be included in the form as a hidden input. +* <> +* <> +* <> + +[[csrf-integration-form]] +=== HTML Forms + +To submit 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 +.CSRF Token in HTML Form ==== [source,html] ---- @@ -252,28 +674,17 @@ For example, the rendered HTML might look like: ---- ==== -Next, we discuss various ways of including the CSRF token in a form as a hidden input. +The following view technologies automatically include the actual CSRF token in a form that has an unsafe HTTP method, such as a POST: -[[servlet-csrf-include-form-auto]] -===== Automatic CSRF Token Inclusion - -Spring Security's CSRF support provides integration with Spring's https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/servlet/support/RequestDataValueProcessor.html[`RequestDataValueProcessor`] through its https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/web/servlet/support/csrf/CsrfRequestDataValueProcessor.html[`CsrfRequestDataValueProcessor`]. -This means that, if you use https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-view-jsp-formtaglib[Spring’s form tag library], https://www.thymeleaf.org/doc/tutorials/2.1/thymeleafspring.html#integration-with-requestdatavalueprocessor[Thymeleaf], or any other view technology that integrates with `RequestDataValueProcessor`, then forms that have an unsafe HTTP method (such as post) automatically include the actual CSRF token. - -[[servlet-csrf-include-form-tag]] -===== csrfInput Tag - -If you use JSPs, you can use https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-view-jsp-formtaglib[Spring’s form tag library]. -However, if that is not an option, you can also include the token with the xref:servlet/integrations/jsp-taglibs.adoc#taglibs-csrfinput[csrfInput] tag. - -[[servlet-csrf-include-form-attr]] -===== CsrfToken Request Attribute - -If the <> for including the actual CSRF token in the request do not work, you can take advantage of the fact that the `CsrfToken` <> as an `HttpServletRequest` attribute named `_csrf`. +* https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-view-jsp-formtaglib[Spring’s form tag library] +* https://www.thymeleaf.org/doc/tutorials/2.1/thymeleafspring.html#integration-with-requestdatavalueprocessor[Thymeleaf] +* Any other view technology that integrates with {spring-framework-api-url}org/springframework/web/servlet/support/RequestDataValueProcessor.html[`RequestDataValueProcessor`] (via {security-api-url}org/springframework/security/web/servlet/support/csrf/CsrfRequestDataValueProcessor.html[`CsrfRequestDataValueProcessor`]) +* You can also include the token yourself via the xref:servlet/integrations/jsp-taglibs.adoc#taglibs-csrfinput[csrfInput] tag +If these options are not available, you can take advantage of the fact that the `CsrfToken` is exposed as an <>. The following example does this with a JSP: -.CSRF Token in Form with Request Attribute +.CSRF Token in HTML Form with Request Attribute ==== [source,xml] ---- @@ -289,26 +700,215 @@ The following example does this with a JSP: ---- ==== -[[servlet-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. +[[csrf-integration-javascript]] +=== JavaScript Applications -The following sections discuss various ways of including the CSRF token as an HTTP request header in JavaScript based applications. +JavaScript applications typically use JSON instead of HTML. +If you use JSON, you can submit the CSRF token within an HTTP request header instead of a request parameter. -[[servlet-csrf-include-ajax-auto]] -===== Automatic Inclusion +In order to obtain the CSRF token, you can configure Spring Security to store the expected CSRF token <>. +By storing the expected token in a cookie, JavaScript frameworks such as https://angular.io/api/common/http/HttpClientXsrfModule[Angular] can automatically include the actual CSRF token as an HTTP request header. -You can <> 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. +[TIP] +==== +There are special considerations for BREACH protection and deferred tokens when integrating a single-page application (SPA) with Spring Security's CSRF protection. +A full configuration example is provided in the <>. +==== -[[servlet-csrf-include-ajax-meta]] -===== Meta Tags +You can read about different types of JavaScript applications in the following sections: -An alternative pattern to <> is to include the CSRF token within your `meta` tags. +* <> +* <> +* <> + +[[csrf-integration-javascript-spa]] +==== Single-Page Applications + +There are special considerations for integrating a single-page application (SPA) with Spring Security's CSRF protection. + +Recall that Spring Security provides <> by default. +When storing the expected CSRF token <>, JavaScript applications will only have access to the plain token value and _will not_ have access to the encoded value. +A <> for resolving the actual token value will need to be provided. + +In addition, the cookie storing the CSRF token will be cleared upon authentication success and logout success. +Spring Security defers loading a new CSRF token by default, and additional work is required to return a fresh cookie. + +[NOTE] +==== +Refreshing the token after authentication success and logout success is required because the {security-api-url}org/springframework/security/web/csrf/CsrfAuthenticationStrategy.html[`CsrfAuthenticationStrategy`] and {security-api-url}org/springframework/security/web/csrf/CsrfLogoutHandler.html[`CsrfLogoutHandler`] will clear the previous token. +The client application will not be able to perform an unsafe HTTP request, such as a POST, without obtaining a fresh token. +==== + +In order to easily integrate a single-page application with Spring Security, the following configuration can be used: + +[[csrf-integration-javascript-spa-configuration]] +.Configure CSRF for Single-Page Application +==== +.Java +[source,java,role="primary"] +---- +@Configuration +@EnableWebSecurity +public class SecurityConfig { + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http + // ... + .csrf((csrf) -> csrf + .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) // <1> + .csrfTokenRequestHandler(new SpaCsrfTokenRequestHandler()) // <2> + ) + .addFilterAfter(new CsrfCookieFilter(), BasicAuthenticationFilter.class); // <3> + return http.build(); + } +} + +final class SpaCsrfTokenRequestHandler extends CsrfTokenRequestAttributeHandler { + private final CsrfTokenRequestHandler delegate = new XorCsrfTokenRequestAttributeHandler(); + + @Override + public void handle(HttpServletRequest request, HttpServletResponse response, Supplier csrfToken) { + /* + * Always use XorCsrfTokenRequestAttributeHandler to provide BREACH protection of + * the CsrfToken when it is rendered in the response body. + */ + this.delegate.handle(request, response, csrfToken); + } + + @Override + public String resolveCsrfTokenValue(HttpServletRequest request, CsrfToken csrfToken) { + /* + * If the request contains a request header, use CsrfTokenRequestAttributeHandler + * to resolve the CsrfToken. This applies when a single-page application includes + * the header value automatically, which was obtained via a cookie containing the + * raw CsrfToken. + */ + if (StringUtils.hasText(request.getHeader(csrfToken.getHeaderName()))) { + return super.resolveCsrfTokenValue(request, csrfToken); + } + /* + * In all other cases (e.g. if the request contains a request parameter), use + * XorCsrfTokenRequestAttributeHandler to resolve the CsrfToken. This applies + * when a server-side rendered form includes the _csrf request parameter as a + * hidden input. + */ + return this.delegate.resolveCsrfTokenValue(request, csrfToken); + } +} + +final class CsrfCookieFilter extends OncePerRequestFilter { + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + CsrfToken csrfToken = (CsrfToken) request.getAttribute("_csrf"); + // 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"] +---- +import org.springframework.security.config.annotation.web.invoke + +@Configuration +@EnableWebSecurity +class SecurityConfig { + + @Bean + open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { + http { + // ... + csrf { + csrfTokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse() // <1> + csrfTokenRequestHandler = SpaCsrfTokenRequestHandler() // <2> + } + } + http.addFilterAfter(CsrfCookieFilter(), BasicAuthenticationFilter::class.java) // <3> + return http.build() + } +} + +class SpaCsrfTokenRequestHandler : CsrfTokenRequestAttributeHandler() { + private val delegate: CsrfTokenRequestHandler = XorCsrfTokenRequestAttributeHandler() + + override fun handle(request: HttpServletRequest, response: HttpServletResponse, csrfToken: Supplier) { + /* + * Always use XorCsrfTokenRequestAttributeHandler to provide BREACH protection of + * the CsrfToken when it is rendered in the response body. + */ + delegate.handle(request, response, csrfToken) + } + + override fun resolveCsrfTokenValue(request: HttpServletRequest, csrfToken: CsrfToken): String { + /* + * If the request contains a request header, use CsrfTokenRequestAttributeHandler + * to resolve the CsrfToken. This applies when a single-page application includes + * the header value automatically, which was obtained via a cookie containing the + * raw CsrfToken. + */ + return if (StringUtils.hasText(request.getHeader(csrfToken.headerName))) { + super.resolveCsrfTokenValue(request, csrfToken) + } else { + /* + * In all other cases (e.g. if the request contains a request parameter), use + * XorCsrfTokenRequestAttributeHandler to resolve the CsrfToken. This applies + * when a server-side rendered form includes the _csrf request parameter as a + * hidden input. + */ + delegate.resolveCsrfTokenValue(request, csrfToken) + } + } +} + +class CsrfCookieFilter : OncePerRequestFilter() { + + @Throws(ServletException::class, IOException::class) + override fun doFilterInternal(request: HttpServletRequest, response: HttpServletResponse, filterChain: FilterChain) { + val csrfToken = request.getAttribute("_csrf") as CsrfToken + // Render the token value to a cookie by causing the deferred token to be loaded + csrfToken.token + filterChain.doFilter(request, response) + } +} +---- + +.XML +[source,xml,role="secondary"] +---- + + + + request-handler-ref="requestHandler"/> <2> + <3> + + + + +---- +==== + +<1> Configure `CookieCsrfTokenRepository` with `HttpOnly` set to `false` so the cookie can be read by the JavaScript application. +<2> Configure a custom `CsrfTokenRequestHandler` that resolves the CSRF token based on whether it is an HTTP request header (`X-XSRF-TOKEN`) or request parameter (`_csrf`). +<3> Configure a custom `Filter` to load the `CsrfToken` on every request, which will return a new cookie if needed. + +[[csrf-integration-javascript-mpa]] +==== Multi-Page Applications + +For multi-page applications where JavaScript is loaded on each page, an alternative to exposing the CSRF token <> is to include the CSRF token within your `meta` tags. The HTML might look something like this: -.CSRF meta tag HTML +.CSRF Token in HTML Meta Tag ==== [source,html] ---- @@ -319,13 +919,33 @@ The HTML might look something like this: + +---- +==== + +In order to include the CSRF token in the request, you can take advantage of the fact that the `CsrfToken` is exposed as an <>. +The following example does this with a JSP: + +.CSRF Token in HTML Meta Tag with Request Attribute +==== +[source,html] +---- + + + + + + + + + ---- ==== 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 can do this with the following code: -.AJAX send CSRF Token +.Include CSRF Token in AJAX Request ==== [source,javascript] ---- @@ -339,72 +959,469 @@ $(function () { ---- ==== -[[servlet-csrf-include-ajax-meta-tag]] -====== csrfMeta tag +[[csrf-integration-javascript-other]] +==== Other JavaScript Applications -If you use JSPs, one way to write the CSRF token to the `meta` tags is by using the xref:servlet/integrations/jsp-taglibs.adoc#taglibs-csrfmeta[csrfMeta] tag. +Another option for JavaScript applications is to include the CSRF token in an HTTP response header. -[[servlet-csrf-include-ajax-meta-attr]] -====== CsrfToken Request Attribute +One way to achieve this is through the use of a `@ControllerAdvice` with the xref:servlet/integrations/mvc.adoc#mvc-csrf-resolver[`CsrfTokenArgumentResolver`]. +The following is an example of `@ControllerAdvice` that applies to all controller endpoints in the application: -If the <> for including the actual CSRF token in the request do not work, you can take advantage of the fact that the `CsrfToken` <> as an `HttpServletRequest` attribute named `_csrf`. -The following example does this with a JSP: - -.CSRF meta tag JSP +[[controller-advice]] +.CSRF Token in HTTP Response Header ==== -[source,html] +.Java +[source,java,role="primary"] ---- - - - - - - - - +@ControllerAdvice +public class CsrfControllerAdvice { + + @ModelAttribute + public void getCsrfToken(HttpServletResponse response, CsrfToken csrfToken) { + response.setHeader(csrfToken.getHeaderName(), csrfToken.getToken()); + } + +} +---- + +.Kotlin +[source,kotlin,role="secondary"] +---- +@ControllerAdvice +class CsrfControllerAdvice { + + @ModelAttribute + fun getCsrfToken(response: HttpServletResponse, csrfToken: CsrfToken) { + response.setHeader(csrfToken.headerName, csrfToken.token) + } + +} ---- ==== -[[servlet-csrf-considerations]] -== CSRF Considerations -There are a few special considerations to consider when implementing protection against CSRF attacks. -This section discusses those considerations as they pertain to servlet environments. -See xref:features/exploits/csrf.adoc#csrf-considerations[CSRF Considerations] for a more general discussion. +[NOTE] +==== +Because this `@ControllerAdvice` applies to all endpoints in the application, it will cause the CSRF token to be loaded on every request, which can negate the benefits of <> when using the <>. +However, this is not usually an issue when using the <>. +==== +[NOTE] +==== +It is important to remember that controller endpoints and controller advice are called _after_ the Spring Security filter chain. +This means that this `@ControllerAdvice` will only be applied if the request passes through the filter chain to your application. +See the configuration for <> for an example of adding a filter to the filter chain for earlier access to the `HttpServletResponse`. +==== -[[servlet-considerations-csrf-login]] -=== Logging In +The CSRF token will now be available in a response header (<> or <> by default) for any custom endpoints the controller advice applies to. +Any request to the backend can be used to obtain the token from the response, and a subsequent request can include the token in a request header with the same name. -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 servlet support does this out of the box. +[[csrf-integration-mobile]] +=== Mobile Applications -[[servlet-considerations-csrf-logout]] -=== Logging Out +Like <>, mobile applications typically use JSON instead of HTML. +A backend application that _does not_ serve browser traffic may choose to <>. +In that case, no additional work is required. -It is important to xref:features/exploits/csrf.adoc#csrf-considerations-logout[require CSRF for log out] requests to protect against forging logout attempts. -If CSRF protection is enabled (the default), Spring Security's `LogoutFilter` to only process HTTP POST. -This ensures that logging out requires a CSRF token and that a malicious user cannot forcibly log out your users. +However, a backend application that also serves browser traffic and therefore _still requires_ CSRF protection may continue to store the `CsrfToken` <> instead of <>. -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 log out confirmation page that performs the POST. +In this case, a typical pattern for integrating with the backend is to expose a `/csrf` endpoint to allow the frontend (mobile or browser client) to request a CSRF token on demand. +The benefit of using this pattern is that the CSRF token <> and only needs to be loaded from the session when a request requires CSRF protection. +The use of a custom endpoint also means the client application can request that a new token be generated on demand (if necessary) by issuing an explicit request. -If you really want to use HTTP GET with logout, you can do so. However, remember that this is generally not recommended. -For example, the following Java Configuration logs out when the `/logout` URL is requested with any HTTP method: +[TIP] +==== +This pattern can be used for any type of application that requires CSRF protection, not just mobile applications. +While this approach isn't typically required in those cases, it is another option for integrating with a CSRF-protected backend. +==== -.Log out with any HTTP method +The following is an example of the `/csrf` endpoint that makes use of the xref:servlet/integrations/mvc.adoc#mvc-csrf-resolver[`CsrfTokenArgumentResolver`]: + +[[csrf-endpoint]] +.The `/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. +==== + +This endpoint should be called to obtain a CSRF token when the application is launched or initialized (e.g. at load time), and also after authentication success and logout success. + +[NOTE] +==== +Refreshing the token after authentication success and logout success is required because the {security-api-url}org/springframework/security/web/csrf/CsrfAuthenticationStrategy.html[`CsrfAuthenticationStrategy`] and {security-api-url}org/springframework/security/web/csrf/CsrfLogoutHandler.html[`CsrfLogoutHandler`] will clear the previous token. +The client application will not be able to perform an unsafe HTTP request, such as a POST, without obtaining a fresh token. +==== + +Once you've obtained the CSRF token, you will need to include it as an HTTP request header (one of <> or <> by default) yourself. + +[[csrf-access-denied-handler]] +== Handle `AccessDeniedException` + +To handle an `AccessDeniedException` such as `InvalidCsrfTokenException`, you can configure Spring Security to handle these exceptions in any way you like. +For example, you can configure a custom access denied page using the following configuration: + +[[csrf-access-denied-handler-configuration]] +.Configure `AccessDeniedHandler` ==== .Java [source,java,role="primary"] ---- @Configuration @EnableWebSecurity -public class WebSecurityConfig { +public class SecurityConfig { @Bean - public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http - .logout(logout -> logout + // ... + .exceptionHandling((exceptionHandling) -> exceptionHandling + .accessDeniedPage("/access-denied") + ); + return http.build(); + } +} +---- + +.Kotlin +[source,kotlin,role="secondary"] +---- +import org.springframework.security.config.annotation.web.invoke + +@Configuration +@EnableWebSecurity +class SecurityConfig { + + @Bean + open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { + http { + // ... + exceptionHandling { + accessDeniedPage = "/access-denied" + } + } + return http.build() + } +} +---- + +.XML +[source,xml,role="secondary"] +---- + + + + +---- +==== + +[[csrf-testing]] +== CSRF Testing + +You can use Spring Security's xref:servlet/test/mockmvc/setup.adoc[testing support] and xref:servlet/test/mockmvc/csrf.adoc[`CsrfRequestPostProcessor`] to test CSRF protection, like this: + +[[csrf-testing-example]] +.Test CSRF Protection +==== +.Java +[source,java,role="primary"] +---- +@ExtendWith(SpringExtension.class) +@ContextConfiguration(classes = SecurityConfig.class) +@WebAppConfiguration +public class CsrfTests { + + private MockMvc mockMvc; + + @BeforeEach + public void setUp(WebApplicationContext applicationContext) { + this.mockMvc = MockMvcBuilders.webAppContextSetup(applicationContext) + .apply(springSecurity()) + .build(); + } + + @Test + public void loginWhenValidCsrfTokenThenSuccess() throws Exception { + this.mockMvc.perform(post("/login").with(csrf()) + .accept(MediaType.TEXT_HTML) + .param("username", "user") + .param("password", "password")) + .andExpect(status().is3xxRedirection()) + .andExpect(header().string(HttpHeaders.LOCATION, "/")); + } + + @Test + @WithMockUser + public void logoutWhenValidCsrfTokenThenSuccess() throws Exception { + this.mockMvc.perform(post("/logout").with(csrf()) + .accept(MediaType.TEXT_HTML)) + .andExpect(status().is3xxRedirection()) + .andExpect(header().string(HttpHeaders.LOCATION, "/login?logout")); + } +} +---- + +.Kotlin +[source,kotlin,role="secondary"] +---- +@ExtendWith(SpringExtension::class) +@ContextConfiguration(classes = [SecurityConfig::class]) +@WebAppConfiguration +class CsrfTests { + private lateinit var mockMvc: MockMvc + + @BeforeEach + fun setUp(applicationContext: WebApplicationContext) { + mockMvc = MockMvcBuilders.webAppContextSetup(applicationContext) + .apply(springSecurity()) + .build() + } + + @Test + fun loginWhenValidCsrfTokenThenSuccess() { + mockMvc.perform(post("/login").with(csrf()) + .accept(MediaType.TEXT_HTML) + .param("username", "user") + .param("password", "password")) + .andExpect(status().is3xxRedirection) + .andExpect(header().string(HttpHeaders.LOCATION, "/")) + } + + @Test + @WithMockUser + @Throws(Exception::class) + fun logoutWhenValidCsrfTokenThenSuccess() { + mockMvc.perform(post("/logout").with(csrf()) + .accept(MediaType.TEXT_HTML)) + .andExpect(status().is3xxRedirection) + .andExpect(header().string(HttpHeaders.LOCATION, "/login?logout")) + } +} +---- +==== + +[TIP] +==== +The above example uses the following static imports: + +===== +.Java +[source,java,role="primary"] +---- +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +---- + +.Kotlin +[source,kotlin,role="secondary"] +---- +import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.* +import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.* +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.* +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.* +---- +===== +==== + +[[disable-csrf]] +== Disable CSRF Protection + +By default, CSRF protection is enabled, which affects <> and <> your application. +Before disabling CSRF protection, consider whether it xref:features/exploits/csrf.adoc#csrf-when[makes sense for your application]. + +You can also consider whether only certain endpoints do not require CSRF protection and configure an ignoring rule, as in the following example: + +[[disable-csrf-ignoring-configuration]] +.Ignoring Requests +==== +.Java +[source,java,role="primary"] +---- +@Configuration +@EnableWebSecurity +public class SecurityConfig { + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http + // ... + .csrf((csrf) -> csrf + .ignoringRequestMatchers("/api/*") + ); + return http.build(); + } +} +---- + +.Kotlin +[source,kotlin,role="secondary"] +---- +import org.springframework.security.config.annotation.web.invoke + +@Configuration +@EnableWebSecurity +class SecurityConfig { + + @Bean + open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { + http { + // ... + csrf { + ignoringRequestMatchers("/api/*") + } + } + return http.build() + } +} +---- + +.XML +[source,xml,role="secondary"] +---- + + + + + + + + + + + + + + +---- +==== + +If you need to disable CSRF protection, you can do so using the following configuration: + +[[disable-csrf-configuration]] +.Disable CSRF +==== +.Java +[source,java,role="primary"] +---- +@Configuration +@EnableWebSecurity +public class SecurityConfig { + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http + // ... + .csrf((csrf) -> csrf.disable()); + return http.build(); + } +} +---- + +.Kotlin +[source,kotlin,role="secondary"] +---- +import org.springframework.security.config.annotation.web.invoke + +@Configuration +@EnableWebSecurity +class SecurityConfig { + + @Bean + open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { + http { + // ... + csrf { + disable() + } + } + return http.build() + } +} +---- + +.XML +[source,xml,role="secondary"] +---- + + + + +---- +==== + +[[csrf-considerations]] +== CSRF Considerations + +There are a few special considerations when implementing protection against CSRF attacks. +This section discusses those considerations as they pertain to servlet environments. +See xref:features/exploits/csrf.adoc#csrf-considerations[CSRF Considerations] for a more general discussion. + +[[csrf-considerations-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 servlet support does this out of the box. + +[[csrf-considerations-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 logout attempts. +If CSRF protection is enabled (the default), Spring Security's `LogoutFilter` will only process HTTP POST requests. +This ensures that logging out requires a CSRF token and that a malicious user cannot forcibly log your users out. + +The easiest approach is to use a form to log the user 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 log out confirmation page that performs the POST. + +If you really want to use HTTP GET with logout, you can do so. +However, remember that this is generally not recommended. +For example, the following logs out when the `/logout` URL is requested with any HTTP method: + +.Log Out with Any HTTP Method +==== +.Java +[source,java,role="primary"] +---- +@Configuration +@EnableWebSecurity +public class SecurityConfig { + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http + // ... + .logout((logout) -> logout .logoutRequestMatcher(new AntPathRequestMatcher("/logout")) ); return http.build(); @@ -415,13 +1432,16 @@ public class WebSecurityConfig { .Kotlin [source,kotlin,role="secondary"] ---- +import org.springframework.security.config.annotation.web.invoke + @Configuration @EnableWebSecurity class SecurityConfig { @Bean - open fun filterChain(http: HttpSecurity): SecurityFilterChain { - http { + open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { + http { + // ... logout { logoutRequestMatcher = AntPathRequestMatcher("/logout") } @@ -432,36 +1452,37 @@ class SecurityConfig { ---- ==== +See the xref:servlet/authentication/logout.adoc[Logout] chapter for more information. -[[servlet-considerations-csrf-timeouts]] +[[considerations-csrf-timeouts]] === CSRF and Session Timeouts -By default, Spring Security stores the CSRF token in the `HttpSession`. +By default, Spring Security stores the CSRF token in the `HttpSession` using the <>. This can lead to a situation where the session expires, leaving no CSRF token to validate against. -We have already discussed xref:features/exploits/csrf.adoc#csrf-considerations-login[general solutions] to session timeouts. +We have already discussed xref:features/exploits/csrf.adoc#csrf-considerations-timeouts[general solutions] to session timeouts. This section discusses the specifics of CSRF timeouts as it pertains to the servlet support. You can change the storage of the CSRF token to be in a cookie. -For details, see the <> section. +For details, see the <> section. -If a token does expire, you might want to customize how it is handled by specifying a custom `AccessDeniedHandler`. +If a token does expire, you might want to customize how it is handled by specifying a <>. The custom `AccessDeniedHandler` can process the `InvalidCsrfTokenException` any way you like. -For an example of how to customize the `AccessDeniedHandler`, see the provided links for both xref:servlet/appendix/namespace/http.adoc#nsa-access-denied-handler[xml] and {gh-url}/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpServerAccessDeniedHandlerTests.java#L64[Java configuration]. -// FIXME: We should add a custom AccessDeniedHandler section in the reference and update the links above - -[[servlet-csrf-considerations-multipart]] +[[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 <> and <> within a servlet application. +When JavaScript is available, we _recommend_ <> to side-step the issue. + +If JavaScript is not available, the following sections discuss options for placing the CSRF token in the <> and <> within a servlet application. [NOTE] ==== -You can find more information about using multipart forms with Spring in the https://docs.spring.io/spring/docs/5.2.x/spring-framework-reference/web.html#mvc-multipart[1.1.11. Multipart Resolver] section of the Spring reference and the https://docs.spring.io/spring/docs/5.2.x/javadoc-api/org/springframework/web/multipart/support/MultipartFilter.html[`MultipartFilter` javadoc]. +You can find more information about using multipart forms with Spring in the https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-multipart[Multipart Resolver] section of the Spring reference and the {spring-framework-api-url}org/springframework/web/multipart/support/MultipartFilter.html[`MultipartFilter` javadoc]. ==== -[[servlet-csrf-considerations-multipart-body]] +[[csrf-considerations-multipart-body]] ==== Place CSRF Token in the Body We have xref:features/exploits/csrf.adoc#csrf-considerations-multipart-body[already discussed] the tradeoffs of placing the CSRF token in the body. @@ -472,11 +1493,7 @@ Specifying the `MultipartFilter` before the Spring Security filter means that th However, only authorized users can submit a file that is processed by your application. In general, this is the recommended approach because the temporary file upload should have a negligible impact on most servers. -// FIXME: Document Spring Boot - -To ensure that `MultipartFilter` is specified before the Spring Security filter with XML configuration, you can ensure the `` element of the `MultipartFilter` is placed before the `springSecurityFilterChain` within the `web.xml` file: - -.Initializer MultipartFilter +.Configure `MultipartFilter` ==== .Java [source,java,role="primary"] @@ -499,13 +1516,9 @@ class SecurityApplicationInitializer : AbstractSecurityWebApplicationInitializer } } ---- -==== -To ensure `MultipartFilter` is specified before the Spring Security filter with XML configuration, users can ensure the element of the `MultipartFilter` is placed before the springSecurityFilterChain within the web.xml as shown below: - -.web.xml - MultipartFilter -==== -[source,xml] +.XML +[source,xml,role="secondary"] ---- MultipartFilter @@ -526,11 +1539,16 @@ To ensure `MultipartFilter` is specified before the Spring Security filter with ---- ==== -[[servlet-csrf-considerations-multipart-url]] +[NOTE] +==== +To ensure that `MultipartFilter` is specified before the Spring Security filter with XML configuration, you can ensure the `` element of the `MultipartFilter` is placed before the `springSecurityFilterChain` within the `web.xml` file. +==== + +[[csrf-considerations-multipart-url]] ==== Include a CSRF Token in a URL If letting unauthorized users upload temporary files is not acceptable, an alternative is to place the `MultipartFilter` after the Spring Security filter and include the CSRF as a query parameter in the action attribute of the form. -Since the `CsrfToken` is exposed as an `HttpServletRequest` <>, we can use that to create an `action` with the CSRF token in it. +Since the `CsrfToken` is exposed as an <>, we can use that to create an `action` with the CSRF token in it. The following example does this with a JSP: .CSRF Token in Action @@ -543,9 +1561,15 @@ The following example does this with a JSP: ---- ==== -[[servlet-csrf-considerations-override-method]] +[[csrf-considerations-override-method]] === HiddenHttpMethodFilter + We have xref:features/exploits/csrf.adoc#csrf-considerations-multipart-body[already discussed] the trade-offs of placing the CSRF token in the body. -In Spring's Servlet support, 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`]. -You can find more information in the 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 Spring's Servlet support, overriding the HTTP method is done by using {spring-framework-api-url}org/springframework/web/filter/reactive/HiddenHttpMethodFilter.html[`HiddenHttpMethodFilter`]. +You can find more information in the https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-rest-method-conversion[HTTP Method Conversion] section of the reference documentation. + +[[csrf-further-reading]] +== Further Reading + +Now that you have reviewed CSRF protection, consider learning more about xref:servlet/exploits/index.adoc[exploit protection] including xref:servlet/exploits/headers.adoc[secure headers] and the xref:servlet/exploits/firewall.adoc[HTTP firewall] or move on to learning how to xref:servlet/test/index.adoc[test] your application. \ No newline at end of file diff --git a/docs/modules/ROOT/pages/servlet/getting-started.adoc b/docs/modules/ROOT/pages/servlet/getting-started.adoc index aa0726e0bb..0dd3af744d 100644 --- a/docs/modules/ROOT/pages/servlet/getting-started.adoc +++ b/docs/modules/ROOT/pages/servlet/getting-started.adoc @@ -173,4 +173,4 @@ In case none of those match what you are looking for, consider thinking about yo For servlet-based applications, Spring Security supports HTTP as well as xref:servlet/integrations/websocket.adoc[Websockets]. 2. *Authentication*: Next, consider how users will xref:servlet/authentication/index.adoc[authenticate] and if that authentication will be stateful or stateless 3. *Authorization*: Then, consider how you will determine xref:servlet/authorization/index.adoc[what a user is authorized to do] -4. *Defense*: Finally, xref:servlet/exploits/csrf.adoc#servlet-csrf-considerations[integrate with Spring Security's default protections] and consider xref:servlet/exploits/headers.adoc[which additional protections you need] +4. *Defense*: Finally, xref:servlet/exploits/csrf.adoc#csrf-considerations[integrate with Spring Security's default protections] and consider xref:servlet/exploits/headers.adoc[which additional protections you need] diff --git a/docs/modules/ROOT/pages/servlet/integrations/mvc.adoc b/docs/modules/ROOT/pages/servlet/integrations/mvc.adoc index 81177d8386..3ddffd726f 100644 --- a/docs/modules/ROOT/pages/servlet/integrations/mvc.adoc +++ b/docs/modules/ROOT/pages/servlet/integrations/mvc.adoc @@ -534,7 +534,7 @@ Spring Security integrates with Spring MVC to add CSRF protection. === Automatic Token Inclusion -Spring Security automatically xref:servlet/exploits/csrf.adoc#servlet-csrf-include[include the CSRF Token] within forms that use the https://docs.spring.io/spring/docs/3.2.x/spring-framework-reference/html/view.html#view-jsp-formtaglib-formtag[Spring MVC form tag]. +Spring Security automatically xref:servlet/exploits/csrf.adoc#csrf-integration-form[include the CSRF Token] within forms that use the https://docs.spring.io/spring/docs/3.2.x/spring-framework-reference/html/view.html#view-jsp-formtaglib-formtag[Spring MVC form tag]. Consider the following JSP: ==== diff --git a/docs/modules/ROOT/pages/servlet/integrations/websocket.adoc b/docs/modules/ROOT/pages/servlet/integrations/websocket.adoc index 380c0397e1..d528ccb2c5 100644 --- a/docs/modules/ROOT/pages/servlet/integrations/websocket.adoc +++ b/docs/modules/ROOT/pages/servlet/integrations/websocket.adoc @@ -281,7 +281,7 @@ Typically we need to include the CSRF token in an HTTP header or an HTTP paramet However, SockJS does not allow for these options. Instead, we must include the token in the Stomp headers. -Applications can xref:servlet/exploits/csrf.adoc#servlet-csrf-include[obtain a CSRF token] by accessing the request attribute named `_csrf`. +Applications can xref:servlet/exploits/csrf.adoc#csrf-integration[obtain a CSRF token] by accessing the request attribute named `_csrf`. For example, the following allows accessing the `CsrfToken` in a JSP: ====