Skip to content

Commit 6b0ed02

Browse files
author
Steve Riesenberg
committed
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
1 parent 33ce3b5 commit 6b0ed02

File tree

2 files changed

+43
-0
lines changed

2 files changed

+43
-0
lines changed

web/src/main/java/org/springframework/security/web/csrf/CookieCsrfTokenRepository.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ public final class CookieCsrfTokenRepository implements CsrfTokenRepository {
4343

4444
static final String DEFAULT_CSRF_HEADER_NAME = "X-XSRF-TOKEN";
4545

46+
private static final String CSRF_TOKEN_REMOVED_ATTRIBUTE_NAME = CookieCsrfTokenRepository.class.getName()
47+
.concat(".REMOVED");
48+
4649
private String parameterName = DEFAULT_CSRF_PARAMETER_NAME;
4750

4851
private String headerName = DEFAULT_CSRF_HEADER_NAME;
@@ -79,10 +82,24 @@ public void saveToken(CsrfToken token, HttpServletRequest request, HttpServletRe
7982
cookie.setDomain(this.cookieDomain);
8083
}
8184
response.addCookie(cookie);
85+
86+
// Set request attribute to signal that response has blank cookie value,
87+
// which allows loadToken to return null when token has been removed
88+
if (!StringUtils.hasLength(tokenValue)) {
89+
request.setAttribute(CSRF_TOKEN_REMOVED_ATTRIBUTE_NAME, Boolean.TRUE);
90+
}
91+
else {
92+
request.removeAttribute(CSRF_TOKEN_REMOVED_ATTRIBUTE_NAME);
93+
}
8294
}
8395

8496
@Override
8597
public CsrfToken loadToken(HttpServletRequest request) {
98+
// Return null when token has been removed during the current request
99+
// which allows loadDeferredToken to re-generate the token
100+
if (Boolean.TRUE.equals(request.getAttribute(CSRF_TOKEN_REMOVED_ATTRIBUTE_NAME))) {
101+
return null;
102+
}
86103
Cookie cookie = WebUtils.getCookie(request, this.cookieName);
87104
if (cookie == null) {
88105
return null;

web/src/test/java/org/springframework/security/web/csrf/CookieCsrfTokenRepositoryTests.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,32 @@ public void loadDeferredTokenWhenDoesNotExistThenGeneratedAndSaved() {
263263
assertThat(tokenCookie.isHttpOnly()).isEqualTo(true);
264264
}
265265

266+
@Test
267+
public void loadDeferredTokenWhenExistsAndNullSavedThenGeneratedAndSaved() {
268+
CsrfToken generatedToken = this.repository.generateToken(this.request);
269+
this.request
270+
.setCookies(new Cookie(CookieCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME, generatedToken.getToken()));
271+
this.repository.saveToken(null, this.request, this.response);
272+
DeferredCsrfToken deferredCsrfToken = this.repository.loadDeferredToken(this.request, this.response);
273+
CsrfToken csrfToken = deferredCsrfToken.get();
274+
assertThat(csrfToken).isNotNull();
275+
assertThat(generatedToken).isNotEqualTo(csrfToken);
276+
assertThat(deferredCsrfToken.isGenerated()).isTrue();
277+
}
278+
279+
@Test
280+
public void loadDeferredTokenWhenExistsAndNullSavedAndNonNullSavedThenLoaded() {
281+
CsrfToken generatedToken = this.repository.generateToken(this.request);
282+
this.request
283+
.setCookies(new Cookie(CookieCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME, generatedToken.getToken()));
284+
this.repository.saveToken(null, this.request, this.response);
285+
this.repository.saveToken(generatedToken, this.request, this.response);
286+
DeferredCsrfToken deferredCsrfToken = this.repository.loadDeferredToken(this.request, this.response);
287+
CsrfToken csrfToken = deferredCsrfToken.get();
288+
assertThatCsrfToken(csrfToken).isEqualTo(generatedToken);
289+
assertThat(deferredCsrfToken.isGenerated()).isFalse();
290+
}
291+
266292
@Test
267293
public void loadDeferredTokenWhenExistsThenLoaded() {
268294
CsrfToken generatedToken = this.repository.generateToken(this.request);

0 commit comments

Comments
 (0)