diff --git a/web/src/main/java/org/springframework/security/web/csrf/CookieCsrfTokenRepository.java b/web/src/main/java/org/springframework/security/web/csrf/CookieCsrfTokenRepository.java index 62075e0d06..7fb066f661 100644 --- a/web/src/main/java/org/springframework/security/web/csrf/CookieCsrfTokenRepository.java +++ b/web/src/main/java/org/springframework/security/web/csrf/CookieCsrfTokenRepository.java @@ -43,6 +43,9 @@ public final class CookieCsrfTokenRepository implements CsrfTokenRepository { static final String DEFAULT_CSRF_HEADER_NAME = "X-XSRF-TOKEN"; + private static final String CSRF_TOKEN_REMOVED_ATTRIBUTE_NAME = CookieCsrfTokenRepository.class.getName() + .concat(".REMOVED"); + private String parameterName = DEFAULT_CSRF_PARAMETER_NAME; private String headerName = DEFAULT_CSRF_HEADER_NAME; @@ -79,10 +82,24 @@ public final class CookieCsrfTokenRepository implements CsrfTokenRepository { cookie.setDomain(this.cookieDomain); } response.addCookie(cookie); + + // Set request attribute to signal that response has blank cookie value, + // which allows loadToken to return null when token has been removed + if (!StringUtils.hasLength(tokenValue)) { + request.setAttribute(CSRF_TOKEN_REMOVED_ATTRIBUTE_NAME, Boolean.TRUE); + } + else { + request.removeAttribute(CSRF_TOKEN_REMOVED_ATTRIBUTE_NAME); + } } @Override public CsrfToken loadToken(HttpServletRequest request) { + // Return null when token has been removed during the current request + // which allows loadDeferredToken to re-generate the token + if (Boolean.TRUE.equals(request.getAttribute(CSRF_TOKEN_REMOVED_ATTRIBUTE_NAME))) { + return null; + } Cookie cookie = WebUtils.getCookie(request, this.cookieName); if (cookie == null) { return null; diff --git a/web/src/test/java/org/springframework/security/web/csrf/CookieCsrfTokenRepositoryTests.java b/web/src/test/java/org/springframework/security/web/csrf/CookieCsrfTokenRepositoryTests.java index b2151ef99b..42cc5b6711 100644 --- a/web/src/test/java/org/springframework/security/web/csrf/CookieCsrfTokenRepositoryTests.java +++ b/web/src/test/java/org/springframework/security/web/csrf/CookieCsrfTokenRepositoryTests.java @@ -262,6 +262,32 @@ public class CookieCsrfTokenRepositoryTests { assertThat(tokenCookie.isHttpOnly()).isEqualTo(true); } + @Test + public void loadDeferredTokenWhenExistsAndNullSavedThenGeneratedAndSaved() { + CsrfToken generatedToken = this.repository.generateToken(this.request); + this.request + .setCookies(new Cookie(CookieCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME, generatedToken.getToken())); + this.repository.saveToken(null, this.request, this.response); + DeferredCsrfToken deferredCsrfToken = this.repository.loadDeferredToken(this.request, this.response); + CsrfToken csrfToken = deferredCsrfToken.get(); + assertThat(csrfToken).isNotNull(); + assertThat(generatedToken).isNotEqualTo(csrfToken); + assertThat(deferredCsrfToken.isGenerated()).isTrue(); + } + + @Test + public void loadDeferredTokenWhenExistsAndNullSavedAndNonNullSavedThenLoaded() { + CsrfToken generatedToken = this.repository.generateToken(this.request); + this.request + .setCookies(new Cookie(CookieCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME, generatedToken.getToken())); + this.repository.saveToken(null, this.request, this.response); + this.repository.saveToken(generatedToken, this.request, this.response); + DeferredCsrfToken deferredCsrfToken = this.repository.loadDeferredToken(this.request, this.response); + CsrfToken csrfToken = deferredCsrfToken.get(); + assertThatCsrfToken(csrfToken).isEqualTo(generatedToken); + assertThat(deferredCsrfToken.isGenerated()).isFalse(); + } + @Test public void loadDeferredTokenWhenExistsThenLoaded() { CsrfToken generatedToken = this.repository.generateToken(this.request);