Просмотр исходного кода

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
Steve Riesenberg 2 лет назад
Родитель
Сommit
6b0ed0205b

+ 17 - 0
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;

+ 26 - 0
web/src/test/java/org/springframework/security/web/csrf/CookieCsrfTokenRepositoryTests.java

@@ -263,6 +263,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);