I am using Spring for a single page stateless REST application. I have enabled CSRF using the cookie token repository. Every request to the application is authenticated. While the CSRF filter is correctly ignoring GET requests the hook into CsrfAuthenticationStrategy as a session authentication strategy causes the token to be changed on every GET request: this is a problem when may requests are fired at once (not out of the ordinary for a single page app). To get around this I've created a copy of CsrfAuthenticationStrategy where I skip the loadToken, saveToken logic for GET requests (see below). This all works fine.
The issue I'm having is I'd like to not completely duplicate CsrfAuthenticationStrategy and instead extend it, do my filtering at the beginning of onAuthenticate and then call super.onAuthenticate if appropriate, but this is not possible due to the class being marked final. Of the classes that implement SessionAuthenticationStrategy only CsrfAuthenticationStrategy and NullAuthenticatedSessionStrategy are marked this way. I am wondering why these two seem to be unique? Can final be removed from these two classes?
I've noticed there are already changes in CsrfAuthenticationStrategy for Spring Security 5.8 which my copy of the class wouldn't have. This creates a bit of a maintenance headache.
private static class ErsCsrfAuthenticationStrategy
implements SessionAuthenticationStrategy {
private final CsrfTokenRepository csrfTokenRepository;
// API matcher
private final AntPathRequestMatcher[] ignoreRequestMatchers = {
new AntPathRequestMatcher("/api/**", "GET"),
new AntPathRequestMatcher("/resources/**", "GET") };
@Override
public void onAuthentication(Authentication authentication,
HttpServletRequest request, HttpServletResponse response) {
// Skip token reset for some request paths
for (AntPathRequestMatcher rm : ignoreRequestMatchers) {
if (rm.matches(request)) {
return;
}
}
// Logic lifted from CsrfAuthenticationStrategy
if (csrfTokenRepository.loadToken(request) != null) {
csrfTokenRepository.saveToken(null, request, response);
CsrfToken newToken = csrfTokenRepository.generateToken(request);
csrfTokenRepository.saveToken(newToken, request, response);
request.setAttribute(CsrfToken.class.getName(), newToken);
request.setAttribute(newToken.getParameterName(), newToken);
}
}
public ErsCsrfAuthenticationStrategy(
CsrfTokenRepository csrfTokenRepository) {
Assert.notNull(csrfTokenRepository,
"csrfTokenRepository cannot be null");
this.csrfTokenRepository = csrfTokenRepository;
}
}
Comment From: sjohnr
Thanks for reaching out, @kev22257!
Have you tried setting http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)? It seems the authentication mechanism are you using in your stateless application is causing the session authentication strategy to be fired on each request. If (for example) you use bearer tokens with BearerTokenAuthenticationFilter (e.g. .oauth2ResourceServer().jwt()), I believe this wouldn't happen any longer.
Instead of extending the class, you can also build a delegating implementation to inherit all the behavior of CsrfAuthenticationStrategy:
private static final class ErsCsrfAuthenticationStrategy implements SessionAuthenticationStrategy {
private final SessionAuthenticationStrategy delegate;
// API matcher
private final AntPathRequestMatcher[] ignoreRequestMatchers = {
new AntPathRequestMatcher("/api/**", "GET"),
new AntPathRequestMatcher("/resources/**", "GET") };
public ErsCsrfAuthenticationStrategy(CsrfTokenRepository csrfTokenRepository) {
this.delegate = new CsrfAuthenticationStrategy(csrfTokenRepository);
}
@Override
public void onAuthentication(Authentication authentication, HttpServletRequest request, HttpServletResponse response) throws SessionAuthenticationException {
// Skip token reset for some request paths
for (AntPathRequestMatcher rm : ignoreRequestMatchers) {
if (rm.matches(request)) {
return;
}
}
this.delegate.onAuthentication(authentication, request, response);
}
}
I'm going to close this issue for now, as the CsrfAuthenticationStrategy can be extended through delegation.
Comment From: kev22257
Very cool, I think I like the SessionAuthenticationStrategy delegate idea. I am setting the session creation policy to stateless, so I'm not sure why onAuthentication is being invoked for every call. Thanks for the input.