123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489 |
- = Exploit Protection Migrations
- The following steps relate to changes around how to configure CSRF.
- == Defer Loading CsrfToken
- 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]]
- .Defer Loading `CsrfToken`
- ====
- .Java
- [source,java,role="primary"]
- ----
- @Bean
- 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");
- http
- // ...
- .csrf((csrf) -> csrf
- .csrfTokenRequestHandler(requestHandler)
- );
- return http.build();
- }
- ----
- .Kotlin
- [source,kotlin,role="secondary"]
- ----
- @Bean
- open fun springSecurity(http: HttpSecurity): SecurityFilterChain {
- val requestHandler = CsrfTokenRequestAttributeHandler()
- // set the name of the attribute the CsrfToken will be populated on
- requestHandler.setCsrfRequestAttributeName("_csrf")
- http {
- csrf {
- csrfTokenRequestHandler = requestHandler
- }
- }
- return http.build()
- }
- ----
- .XML
- [source,xml,role="secondary"]
- ----
- <http>
- <!-- ... -->
- <csrf request-handler-ref="requestHandler"/>
- </http>
- <b:bean id="requestHandler"
- class="org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler"
- p:csrfRequestAttributeName="_csrf"/>
- ----
- ====
- [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
- ====
- .Java
- [source,java,role="primary"]
- ----
- @Bean
- 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);
- http
- // ...
- .csrf((csrf) -> csrf
- .csrfTokenRequestHandler(requestHandler)
- );
- return http.build();
- }
- ----
- .Kotlin
- [source,kotlin,role="secondary"]
- ----
- @Bean
- open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
- val requestHandler = CsrfTokenRequestAttributeHandler()
- // 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"]
- ----
- <http>
- <!-- ... -->
- <csrf request-handler-ref="requestHandler"/>
- </http>
- <b:bean id="requestHandler"
- class="org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler">
- <b:property name="csrfRequestAttributeName">
- <b:null/>
- </b:property>
- </b:bean>
- ----
- ====
- [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:
- .`CsrfToken` BREACH Protection
- ====
- .Java
- [source,java,role="primary"]
- ----
- @Bean
- DefaultSecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
- XorCsrfTokenRequestAttributeHandler requestHandler = new XorCsrfTokenRequestAttributeHandler();
- // set the name of the attribute the CsrfToken will be populated on
- requestHandler.setCsrfRequestAttributeName("_csrf");
- http
- // ...
- .csrf((csrf) -> csrf
- .csrfTokenRequestHandler(requestHandler)
- );
- return http.build();
- }
- ----
- .Kotlin
- [source,kotlin,role="secondary"]
- ----
- @Bean
- open fun springSecurity(http: HttpSecurity): SecurityFilterChain {
- val requestHandler = XorCsrfTokenRequestAttributeHandler()
- // set the name of the attribute the CsrfToken will be populated on
- requestHandler.setCsrfRequestAttributeName("_csrf")
- http {
- csrf {
- csrfTokenRequestHandler = requestHandler
- }
- }
- return http.build()
- }
- ----
- .XML
- [source,xml,role="secondary"]
- ----
- <http>
- <!-- ... -->
- <csrf request-handler-ref="requestHandler"/>
- </http>
- <b:bean id="requestHandler"
- class="org.springframework.security.web.csrf.XorCsrfTokenRequestAttributeHandler"
- p:csrfRequestAttributeName="_csrf"/>
- ----
- ====
- [[servlet-csrf-breach-opt-out]]
- === Opt-out Steps
- If configuring CSRF BREACH protection gives you trouble, take a look at these scenarios for optimal opt out behavior:
- ==== I am using AngularJS or another Javascript framework
- If you are using AngularJS and the https://angular.io/api/common/http/HttpClientXsrfModule[HttpClientXsrfModule] (or a similar module in another framework) along with `CookieCsrfTokenRepository.withHttpOnlyFalse()`, you may find that automatic support no longer works.
- In this case, you can configure Spring Security to validate the raw `CsrfToken` from the cookie while keeping CSRF BREACH protection of the response using a custom `CsrfTokenRequestHandler` with delegation, like so:
- .Configure `CsrfToken` BREACH Protection to validate raw tokens
- ====
- .Java
- [source,java,role="primary"]
- ----
- @Bean
- public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
- CookieCsrfTokenRepository tokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse();
- XorCsrfTokenRequestAttributeHandler delegate = new XorCsrfTokenRequestAttributeHandler();
- // set the name of the attribute the CsrfToken will be populated on
- delegate.setCsrfRequestAttributeName("_csrf");
- // Use only the handle() method of XorCsrfTokenRequestAttributeHandler and the
- // default implementation of resolveCsrfTokenValue() from CsrfTokenRequestHandler
- CsrfTokenRequestHandler requestHandler = delegate::handle;
- http
- // ...
- .csrf((csrf) -> csrf
- .csrfTokenRepository(tokenRepository)
- .csrfTokenRequestHandler(requestHandler)
- );
- return http.build();
- }
- ----
- .Kotlin
- [source,kotlin,role="secondary"]
- ----
- @Bean
- open fun springSecurity(http: HttpSecurity): SecurityFilterChain {
- val tokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse()
- val delegate = XorCsrfTokenRequestAttributeHandler()
- // set the name of the attribute the CsrfToken will be populated on
- delegate.setCsrfRequestAttributeName("_csrf")
- // Use only the handle() method of XorCsrfTokenRequestAttributeHandler and the
- // default implementation of resolveCsrfTokenValue() from CsrfTokenRequestHandler
- val requestHandler = CsrfTokenRequestHandler(delegate::handle)
- http {
- csrf {
- csrfTokenRepository = tokenRepository
- csrfTokenRequestHandler = requestHandler
- }
- }
- return http.build()
- }
- ----
- .XML
- [source,xml,role="secondary"]
- ----
- <http>
- <!-- ... -->
- <csrf token-repository-ref="tokenRepository"
- request-handler-ref="requestHandler"/>
- </http>
- <b:bean id="tokenRepository"
- class="org.springframework.security.web.csrf.CookieCsrfTokenRepository"
- p:cookieHttpOnly="false"/>
- ----
- ====
- 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.
- == CSRF BREACH with WebSocket support
- If the steps for <<Protect against CSRF BREACH>> work for normal HTTP requests and you are using xref:servlet/integrations/websocket.adoc[WebSocket Security] support, then you can also opt into Spring Security 6's default support for BREACH protection of the `CsrfToken` with xref:servlet/integrations/websocket.adoc#websocket-sameorigin-csrf[Stomp headers].
- .WebSocket Security BREACH Protection
- ====
- .Java
- [source,java,role="primary"]
- ----
- @Bean
- ChannelInterceptor csrfChannelInterceptor() {
- return new XorCsrfChannelInterceptor();
- }
- ----
- .Kotlin
- [source,kotlin,role="secondary"]
- ----
- @Bean
- open fun csrfChannelInterceptor(): ChannelInterceptor {
- return XorCsrfChannelInterceptor()
- }
- ----
- .XML
- [source,xml,role="secondary"]
- ----
- <b:bean id="csrfChannelInterceptor"
- class="org.springframework.security.messaging.web.csrf.XorCsrfChannelInterceptor"/>
- ----
- ====
- If configuring CSRF BREACH protection for WebSocket Security gives you trouble, you can configure the 5.8 default using the following configuration:
- .Configure WebSocket Security with 5.8 default
- ====
- .Java
- [source,java,role="primary"]
- ----
- @Bean
- ChannelInterceptor csrfChannelInterceptor() {
- return new CsrfChannelInterceptor();
- }
- ----
- .Kotlin
- [source,kotlin,role="secondary"]
- ----
- @Bean
- open fun csrfChannelInterceptor(): ChannelInterceptor {
- return CsrfChannelInterceptor()
- }
- ----
- .XML
- [source,xml,role="secondary"]
- ----
- <b:bean id="csrfChannelInterceptor"
- class="org.springframework.security.messaging.web.csrf.CsrfChannelInterceptor"/>
- ----
- ====
|