Re-generate tokens in CookieCsrfTokenRepository

Fixes support for re-generating tokens within a request such as when
CsrfAuthenticationStrategy removes a null token and saves an empty
cookie value on the response.

Closes gh-12141
This commit is contained in:
Steve Riesenberg 2022-11-04 18:09:23 -05:00
parent 33ce3b59b8
commit 6b0ed0205b
No known key found for this signature in database
GPG Key ID: 5F311AB48A55D521
2 changed files with 43 additions and 0 deletions

View File

@ -43,6 +43,9 @@ public final class CookieCsrfTokenRepository implements CsrfTokenRepository {
static final String DEFAULT_CSRF_HEADER_NAME = "X-XSRF-TOKEN"; 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 parameterName = DEFAULT_CSRF_PARAMETER_NAME;
private String headerName = DEFAULT_CSRF_HEADER_NAME; private String headerName = DEFAULT_CSRF_HEADER_NAME;
@ -79,10 +82,24 @@ public final class CookieCsrfTokenRepository implements CsrfTokenRepository {
cookie.setDomain(this.cookieDomain); cookie.setDomain(this.cookieDomain);
} }
response.addCookie(cookie); 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 @Override
public CsrfToken loadToken(HttpServletRequest request) { 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); Cookie cookie = WebUtils.getCookie(request, this.cookieName);
if (cookie == null) { if (cookie == null) {
return null; return null;

View File

@ -263,6 +263,32 @@ public class CookieCsrfTokenRepositoryTests {
assertThat(tokenCookie.isHttpOnly()).isEqualTo(true); 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 @Test
public void loadDeferredTokenWhenExistsThenLoaded() { public void loadDeferredTokenWhenExistsThenLoaded() {
CsrfToken generatedToken = this.repository.generateToken(this.request); CsrfToken generatedToken = this.repository.generateToken(this.request);