For the latest stable version, please use Spring Security 6.3.4! |
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.
Some examples of where it should be unnecessary to read the session include endpoints marked |
In Spring Security 6, the default is that the lookup of the CsrfToken
will be deferred until it is needed.
The |
To opt into the new Spring Security 6 default, the following configuration can be used.
CsrfToken
-
Java
-
Kotlin
-
XML
@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();
}
@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()
}
<http>
<!-- ... -->
<csrf request-handler-ref="requestHandler"/>
</http>
<b:bean id="requestHandler"
class="org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler"
p:csrfRequestAttributeName="_csrf"/>
When the |
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:
Filter
to return a cookie on the response-
Java
-
Kotlin
@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);
}
}
@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:
/csrf
endpoint-
Java
-
Kotlin
@RestController
public class CsrfController {
@GetMapping("/csrf")
public CsrfToken csrf(CsrfToken csrfToken) {
return csrfToken;
}
}
@RestController
class CsrfController {
@GetMapping("/csrf")
fun csrf(csrfToken: CsrfToken): CsrfToken {
return csrfToken
}
}
You may consider adding |
The /csrf
endpoint would need to be consumed by the client-side application in order to bootstrap the application for subsequent requests.
Instructions for calling the |
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 |
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:
CsrfToken
with 5.8 Defaults-
Java
-
Kotlin
-
XML
@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();
}
@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()
}
<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>
By setting the |
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
-
Kotlin
-
XML
@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();
}
@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()
}
<http>
<!-- ... -->
<csrf request-handler-ref="requestHandler"/>
</http>
<b:bean id="requestHandler"
class="org.springframework.security.web.csrf.XorCsrfTokenRequestAttributeHandler"
p:csrfRequestAttributeName="_csrf"/>
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 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:
CsrfToken
BREACH Protection to validate raw tokens-
Java
-
Kotlin
-
XML
@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();
}
@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()
}
<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.
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. |
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 Defer Loading CsrfToken
section.
CSRF BREACH with WebSocket support
If the steps for Protect against CSRF BREACH work for normal HTTP requests and you are using WebSocket Security support, then you can also opt into Spring Security 6’s default support for BREACH protection of the CsrfToken
with Stomp headers.
-
Java
-
Kotlin
-
XML
@Bean
ChannelInterceptor csrfChannelInterceptor() {
return new XorCsrfChannelInterceptor();
}
@Bean
open fun csrfChannelInterceptor(): ChannelInterceptor {
return XorCsrfChannelInterceptor()
}
<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:
-
Java
-
Kotlin
-
XML
@Bean
ChannelInterceptor csrfChannelInterceptor() {
return new CsrfChannelInterceptor();
}
@Bean
open fun csrfChannelInterceptor(): ChannelInterceptor {
return CsrfChannelInterceptor()
}
<b:bean id="csrfChannelInterceptor"
class="org.springframework.security.messaging.web.csrf.CsrfChannelInterceptor"/>