Spring Single Page Application: CSRF token changin

2020-02-14 05:50发布

问题:

As I know, in a Spring+JavaScript single page application, we need to send the CSRF token to the client in some way.

A recommended way would be to have a CsrfHeaderFilter as described in this Spring guide. Following this approach, when the application will start, it will send a GET request to the server, thus fetching the token.

But I see that under certain events like login or logout, Spring Security changes the token. CsrfHeaderFilter comes beforehand, and so can't detect the change. Hence, I am needing to send another GET request following such events.

I tried peeping at the Spring Security code to find if there would be a way to send the changed token along with these login or logout requests, so that another GET request is saved. But, could not find a way.

Liked to know if sending a dummy GET request after login, logout etc., as I am now doing, looks like a good solution. Or, maybe there is a better way?

If there is currently no way to avoid this redundant GET request, I was wondering if this becomes a ticket for Spring Security to come up with something after which it would be possible.

回答1:

Came across with the similar situation regarding CookieCsrfTokenRepository.

The application I worked on has a custom implementation of login with via REST-service. The service has httpServletRequest.logout() call inside that (as I figured out) led to clearing of XSRF-TOKEN cookies in response:

Set-Cookie:XSRF-TOKEN=; Max-Age=0; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Path=/ibdrs; Secure

Without new XSRF-TOKEN value in the answer I had two options:

  1. Make a dummy get request to obtain new XSRF-TOKEN right after login (as the OP suggested)

  2. Get updated XSRF-TOKEN in the same login response instead of clearing cookie.

It turned out that the second option could be achieved by:

  1. Made my own CustomCookieCsrfTokenRepository as the copy of CookieCsrfTokenRepository (the source code is here). If it wasn't final it would be sufficient to extend it instead of copying.

  2. Changed all the occurrences of CookieCsrfTokenRepository to CustomCookieCsrfTokenRepository inside the copy

  3. Replaced saveToken method with new version that never clears the cookie:

    @Override
    public void saveToken(CsrfToken token, HttpServletRequest request,
                      HttpServletResponse response) {
        if (token == null) {
            token = generateToken(request);
        }
        String tokenValue = token.getToken();
        Cookie cookie = new Cookie(this.cookieName, tokenValue);
        cookie.setSecure(request.isSecure());
        if (this.cookiePath != null && !this.cookiePath.isEmpty()) {
            cookie.setPath(this.cookiePath);
        } else {
            cookie.setPath(this.getRequestContext(request));
        }
        cookie.setMaxAge(-1);
        if (cookieHttpOnly && setHttpOnlyMethod != null) {
            ReflectionUtils.invokeMethod(setHttpOnlyMethod, cookie, Boolean.TRUE);
        }
        response.addCookie(cookie);
    }
    
  4. Configured HttpSecurity to use the new class:

    .csrf()
        .csrfTokenRepository(CustomCookieCsrfTokenRepository.withHttpOnlyFalse())
        .and()