Add setCookieCustomizer to csrf token repository
- Mark setCookieHttpOnly, setCookieDomain, setCookieMaxAge and setSecure as deprecated. - Add the method setCookieCustomizer which allows to set properties to the ResponseCookieBuilder without having to add new setter methods. Closes gh-12086
This commit is contained in:
parent
cd0f02de49
commit
b79ba89eeb
|
@ -15,14 +15,15 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.springframework.security.web.csrf;
|
package org.springframework.security.web.csrf;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import jakarta.servlet.ServletRequest;
|
|
||||||
import jakarta.servlet.http.Cookie;
|
import jakarta.servlet.http.Cookie;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.ResponseCookie;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.web.util.WebUtils;
|
import org.springframework.web.util.WebUtils;
|
||||||
|
@ -34,6 +35,7 @@ import org.springframework.web.util.WebUtils;
|
||||||
*
|
*
|
||||||
* @author Rob Winch
|
* @author Rob Winch
|
||||||
* @author Steve Riesenberg
|
* @author Steve Riesenberg
|
||||||
|
* @author Alex Montoya
|
||||||
* @since 4.1
|
* @since 4.1
|
||||||
*/
|
*/
|
||||||
public final class CookieCsrfTokenRepository implements CsrfTokenRepository {
|
public final class CookieCsrfTokenRepository implements CsrfTokenRepository {
|
||||||
|
@ -63,7 +65,17 @@ public final class CookieCsrfTokenRepository implements CsrfTokenRepository {
|
||||||
|
|
||||||
private int cookieMaxAge = -1;
|
private int cookieMaxAge = -1;
|
||||||
|
|
||||||
public CookieCsrfTokenRepository() {
|
private Consumer<ResponseCookie.ResponseCookieBuilder> cookieCustomizer = (builder) -> {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a {@link Consumer} for a {@code ResponseCookieBuilder} that will be invoked
|
||||||
|
* for each cookie being built, just before the call to {@code build()}.
|
||||||
|
* @param cookieCustomizer consumer for a cookie builder
|
||||||
|
* @since 6.1
|
||||||
|
*/
|
||||||
|
public void setCookieCustomizer(Consumer<ResponseCookie.ResponseCookieBuilder> cookieCustomizer) {
|
||||||
|
Assert.notNull(cookieCustomizer, "cookieCustomizer must not be null");
|
||||||
|
this.cookieCustomizer = cookieCustomizer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -74,15 +86,17 @@ public final class CookieCsrfTokenRepository implements CsrfTokenRepository {
|
||||||
@Override
|
@Override
|
||||||
public void saveToken(CsrfToken token, HttpServletRequest request, HttpServletResponse response) {
|
public void saveToken(CsrfToken token, HttpServletRequest request, HttpServletResponse response) {
|
||||||
String tokenValue = (token != null) ? token.getToken() : "";
|
String tokenValue = (token != null) ? token.getToken() : "";
|
||||||
Cookie cookie = new Cookie(this.cookieName, tokenValue);
|
|
||||||
cookie.setSecure((this.secure != null) ? this.secure : request.isSecure());
|
ResponseCookie.ResponseCookieBuilder cookieBuilder = ResponseCookie.from(this.cookieName, tokenValue)
|
||||||
cookie.setPath(StringUtils.hasLength(this.cookiePath) ? this.cookiePath : this.getRequestContext(request));
|
.secure(this.secure != null ? this.secure : request.isSecure())
|
||||||
cookie.setMaxAge((token != null) ? this.cookieMaxAge : 0);
|
.path(StringUtils.hasLength(this.cookiePath) ? this.cookiePath : this.getRequestContext(request))
|
||||||
cookie.setHttpOnly(this.cookieHttpOnly);
|
.maxAge(token != null ? this.cookieMaxAge : 0)
|
||||||
if (StringUtils.hasLength(this.cookieDomain)) {
|
.httpOnly(this.cookieHttpOnly)
|
||||||
cookie.setDomain(this.cookieDomain);
|
.domain(this.cookieDomain);
|
||||||
}
|
|
||||||
response.addCookie(cookie);
|
this.cookieCustomizer.accept(cookieBuilder);
|
||||||
|
|
||||||
|
response.setHeader(HttpHeaders.SET_COOKIE, cookieBuilder.build().toString());
|
||||||
|
|
||||||
// Set request attribute to signal that response has blank cookie value,
|
// Set request attribute to signal that response has blank cookie value,
|
||||||
// which allows loadToken to return null when token has been removed
|
// which allows loadToken to return null when token has been removed
|
||||||
|
@ -143,11 +157,9 @@ public final class CookieCsrfTokenRepository implements CsrfTokenRepository {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the HttpOnly attribute on the cookie containing the CSRF token. Defaults to
|
* @deprecated Use {@link #setCookieCustomizer(Consumer)} instead.
|
||||||
* <code>true</code>.
|
|
||||||
* @param cookieHttpOnly <code>true</code> sets the HttpOnly attribute,
|
|
||||||
* <code>false</code> does not set it
|
|
||||||
*/
|
*/
|
||||||
|
@Deprecated(since = "6.1")
|
||||||
public void setCookieHttpOnly(boolean cookieHttpOnly) {
|
public void setCookieHttpOnly(boolean cookieHttpOnly) {
|
||||||
this.cookieHttpOnly = cookieHttpOnly;
|
this.cookieHttpOnly = cookieHttpOnly;
|
||||||
}
|
}
|
||||||
|
@ -191,51 +203,30 @@ public final class CookieCsrfTokenRepository implements CsrfTokenRepository {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the domain of the cookie that the expected CSRF token is saved to and read
|
* @deprecated Use {@link #setCookieCustomizer(Consumer)} instead.
|
||||||
* from.
|
|
||||||
* @param cookieDomain the domain of the cookie that the expected CSRF token is saved
|
|
||||||
* to and read from
|
|
||||||
* @since 5.2
|
* @since 5.2
|
||||||
*/
|
*/
|
||||||
|
@Deprecated(since = "6.1")
|
||||||
public void setCookieDomain(String cookieDomain) {
|
public void setCookieDomain(String cookieDomain) {
|
||||||
this.cookieDomain = cookieDomain;
|
this.cookieDomain = cookieDomain;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets secure flag of the cookie that the expected CSRF token is saved to and read
|
* @deprecated Use {@link #setCookieCustomizer(Consumer)} instead.
|
||||||
* from. By default secure flag depends on {@link ServletRequest#isSecure()}
|
|
||||||
* @param secure the secure flag of the cookie that the expected CSRF token is saved
|
|
||||||
* to and read from
|
|
||||||
* @since 5.4
|
* @since 5.4
|
||||||
*/
|
*/
|
||||||
|
@Deprecated(since = "6.1")
|
||||||
public void setSecure(Boolean secure) {
|
public void setSecure(Boolean secure) {
|
||||||
this.secure = secure;
|
this.secure = secure;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets maximum age in seconds for the cookie that the expected CSRF token is saved to
|
* @deprecated Use {@link #setCookieCustomizer(Consumer)} instead.
|
||||||
* and read from. By default maximum age value is -1.
|
|
||||||
*
|
|
||||||
* <p>
|
|
||||||
* A positive value indicates that the cookie will expire after that many seconds have
|
|
||||||
* passed. Note that the value is the <i>maximum</i> age when the cookie will expire,
|
|
||||||
* not the cookie's current age.
|
|
||||||
*
|
|
||||||
* <p>
|
|
||||||
* A negative value means that the cookie is not stored persistently and will be
|
|
||||||
* deleted when the Web browser exits.
|
|
||||||
*
|
|
||||||
* <p>
|
|
||||||
* A zero value causes the cookie to be deleted immediately therefore it is not a
|
|
||||||
* valid value and in that case an {@link IllegalArgumentException} will be thrown.
|
|
||||||
* @param cookieMaxAge an integer specifying the maximum age of the cookie in seconds;
|
|
||||||
* if negative, means the cookie is not stored; if zero, the method throws an
|
|
||||||
* {@link IllegalArgumentException}
|
|
||||||
* @since 5.5
|
* @since 5.5
|
||||||
*/
|
*/
|
||||||
|
@Deprecated(since = "6.1")
|
||||||
public void setCookieMaxAge(int cookieMaxAge) {
|
public void setCookieMaxAge(int cookieMaxAge) {
|
||||||
Assert.isTrue(cookieMaxAge != 0, "cookieMaxAge cannot be zero");
|
Assert.isTrue(cookieMaxAge != 0, "cookieMaxAge cannot be zero");
|
||||||
this.cookieMaxAge = cookieMaxAge;
|
this.cookieMaxAge = cookieMaxAge;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
package org.springframework.security.web.server.csrf;
|
package org.springframework.security.web.server.csrf;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import reactor.core.scheduler.Schedulers;
|
import reactor.core.scheduler.Schedulers;
|
||||||
|
@ -36,6 +37,7 @@ import org.springframework.web.server.ServerWebExchange;
|
||||||
* @author Eric Deandrea
|
* @author Eric Deandrea
|
||||||
* @author Thomas Vitale
|
* @author Thomas Vitale
|
||||||
* @author Alonso Araya
|
* @author Alonso Araya
|
||||||
|
* @author Alex Montoya
|
||||||
* @since 5.1
|
* @since 5.1
|
||||||
*/
|
*/
|
||||||
public final class CookieServerCsrfTokenRepository implements ServerCsrfTokenRepository {
|
public final class CookieServerCsrfTokenRepository implements ServerCsrfTokenRepository {
|
||||||
|
@ -60,6 +62,19 @@ public final class CookieServerCsrfTokenRepository implements ServerCsrfTokenRep
|
||||||
|
|
||||||
private int cookieMaxAge = -1;
|
private int cookieMaxAge = -1;
|
||||||
|
|
||||||
|
private Consumer<ResponseCookie.ResponseCookieBuilder> cookieCustomizer = (builder) -> {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a {@link Consumer} for a {@code ResponseCookieBuilder} that will be invoked
|
||||||
|
* for each cookie being built, just before the call to {@code build()}.
|
||||||
|
* @param cookieCustomizer consumer for a cookie builder
|
||||||
|
* @since 6.1
|
||||||
|
*/
|
||||||
|
public void setCookieCustomizer(Consumer<ResponseCookie.ResponseCookieBuilder> cookieCustomizer) {
|
||||||
|
Assert.notNull(cookieCustomizer, "cookieCustomizer must not be null");
|
||||||
|
this.cookieCustomizer = cookieCustomizer;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Factory method to conveniently create an instance that has
|
* Factory method to conveniently create an instance that has
|
||||||
* {@link #setCookieHttpOnly(boolean)} set to false.
|
* {@link #setCookieHttpOnly(boolean)} set to false.
|
||||||
|
@ -82,16 +97,18 @@ public final class CookieServerCsrfTokenRepository implements ServerCsrfTokenRep
|
||||||
return Mono.fromRunnable(() -> {
|
return Mono.fromRunnable(() -> {
|
||||||
String tokenValue = (token != null) ? token.getToken() : "";
|
String tokenValue = (token != null) ? token.getToken() : "";
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
ResponseCookie cookie = ResponseCookie
|
ResponseCookie.ResponseCookieBuilder cookieBuilder = ResponseCookie
|
||||||
.from(this.cookieName, tokenValue)
|
.from(this.cookieName, tokenValue)
|
||||||
.domain(this.cookieDomain)
|
.domain(this.cookieDomain)
|
||||||
.httpOnly(this.cookieHttpOnly)
|
.httpOnly(this.cookieHttpOnly)
|
||||||
.maxAge(!tokenValue.isEmpty() ? this.cookieMaxAge : 0)
|
.maxAge(!tokenValue.isEmpty() ? this.cookieMaxAge : 0)
|
||||||
.path((this.cookiePath != null) ? this.cookiePath : getRequestContext(exchange.getRequest()))
|
.path((this.cookiePath != null) ? this.cookiePath : getRequestContext(exchange.getRequest()))
|
||||||
.secure((this.secure != null) ? this.secure : (exchange.getRequest().getSslInfo() != null))
|
.secure((this.secure != null) ? this.secure : (exchange.getRequest().getSslInfo() != null));
|
||||||
.build();
|
|
||||||
|
this.cookieCustomizer.accept(cookieBuilder);
|
||||||
|
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
exchange.getResponse().addCookie(cookie);
|
exchange.getResponse().addCookie(cookieBuilder.build());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,9 +124,9 @@ public final class CookieServerCsrfTokenRepository implements ServerCsrfTokenRep
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the HttpOnly attribute on the cookie containing the CSRF token
|
* @deprecated Use {@link #setCookieCustomizer(Consumer)} instead.
|
||||||
* @param cookieHttpOnly True to mark the cookie as http only. False otherwise.
|
|
||||||
*/
|
*/
|
||||||
|
@Deprecated(since = "6.1")
|
||||||
public void setCookieHttpOnly(boolean cookieHttpOnly) {
|
public void setCookieHttpOnly(boolean cookieHttpOnly) {
|
||||||
this.cookieHttpOnly = cookieHttpOnly;
|
this.cookieHttpOnly = cookieHttpOnly;
|
||||||
}
|
}
|
||||||
|
@ -150,44 +167,27 @@ public final class CookieServerCsrfTokenRepository implements ServerCsrfTokenRep
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the cookie domain
|
* @deprecated Use {@link #setCookieCustomizer(Consumer)} instead.
|
||||||
* @param cookieDomain The cookie domain
|
|
||||||
*/
|
*/
|
||||||
|
@Deprecated(since = "6.1")
|
||||||
public void setCookieDomain(String cookieDomain) {
|
public void setCookieDomain(String cookieDomain) {
|
||||||
this.cookieDomain = cookieDomain;
|
this.cookieDomain = cookieDomain;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the cookie secure flag. If not set, the value depends on
|
* @deprecated Use {@link #setCookieCustomizer(Consumer)} instead.
|
||||||
* {@link ServerHttpRequest#getSslInfo()}.
|
|
||||||
* @param secure The value for the secure flag
|
|
||||||
* @since 5.5
|
* @since 5.5
|
||||||
*/
|
*/
|
||||||
|
@Deprecated(since = "6.1")
|
||||||
public void setSecure(boolean secure) {
|
public void setSecure(boolean secure) {
|
||||||
this.secure = secure;
|
this.secure = secure;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets maximum age in seconds for the cookie that the expected CSRF token is saved to
|
* @deprecated Use {@link #setCookieCustomizer(Consumer)} instead.
|
||||||
* and read from. By default maximum age value is -1.
|
|
||||||
*
|
|
||||||
* <p>
|
|
||||||
* A positive value indicates that the cookie will expire after that many seconds have
|
|
||||||
* passed. Note that the value is the <i>maximum</i> age when the cookie will expire,
|
|
||||||
* not the cookie's current age.
|
|
||||||
*
|
|
||||||
* <p>
|
|
||||||
* A negative value means that the cookie is not stored persistently and will be
|
|
||||||
* deleted when the Web browser exits.
|
|
||||||
*
|
|
||||||
* <p>
|
|
||||||
* A zero value causes the cookie to be deleted immediately therefore it is not a
|
|
||||||
* valid value and in that case an {@link IllegalArgumentException} will be thrown.
|
|
||||||
* @param cookieMaxAge an integer specifying the maximum age of the cookie in seconds;
|
|
||||||
* if negative, means the cookie is not stored; if zero, the method throws an
|
|
||||||
* {@link IllegalArgumentException}
|
|
||||||
* @since 5.8
|
* @since 5.8
|
||||||
*/
|
*/
|
||||||
|
@Deprecated(since = "6.1")
|
||||||
public void setCookieMaxAge(int cookieMaxAge) {
|
public void setCookieMaxAge(int cookieMaxAge) {
|
||||||
Assert.isTrue(cookieMaxAge != 0, "cookieMaxAge cannot be zero");
|
Assert.isTrue(cookieMaxAge != 0, "cookieMaxAge cannot be zero");
|
||||||
this.cookieMaxAge = cookieMaxAge;
|
this.cookieMaxAge = cookieMaxAge;
|
||||||
|
|
|
@ -20,6 +20,7 @@ import jakarta.servlet.http.Cookie;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import org.springframework.mock.web.MockCookie;
|
||||||
import org.springframework.mock.web.MockHttpServletRequest;
|
import org.springframework.mock.web.MockHttpServletRequest;
|
||||||
import org.springframework.mock.web.MockHttpServletResponse;
|
import org.springframework.mock.web.MockHttpServletResponse;
|
||||||
|
|
||||||
|
@ -29,6 +30,7 @@ import static org.springframework.security.web.csrf.CsrfTokenAssert.assertThatCs
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Rob Winch
|
* @author Rob Winch
|
||||||
|
* @author Alex Montoya
|
||||||
* @since 4.1
|
* @since 4.1
|
||||||
*/
|
*/
|
||||||
public class CookieCsrfTokenRepositoryTests {
|
public class CookieCsrfTokenRepositoryTests {
|
||||||
|
@ -102,7 +104,17 @@ public class CookieCsrfTokenRepositoryTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void saveTokenSecureFlagFalse() {
|
void saveTokenSecureFlagTrueUsingCustomizer() {
|
||||||
|
this.request.setSecure(false);
|
||||||
|
this.repository.setCookieCustomizer(customizer -> customizer.secure(Boolean.TRUE));
|
||||||
|
CsrfToken token = this.repository.generateToken(this.request);
|
||||||
|
this.repository.saveToken(token, this.request, this.response);
|
||||||
|
Cookie tokenCookie = this.response.getCookie(CookieCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME);
|
||||||
|
assertThat(tokenCookie.getSecure()).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void saveTokenSecureFlagFalse() {
|
||||||
this.request.setSecure(true);
|
this.request.setSecure(true);
|
||||||
this.repository.setSecure(Boolean.FALSE);
|
this.repository.setSecure(Boolean.FALSE);
|
||||||
CsrfToken token = this.repository.generateToken(this.request);
|
CsrfToken token = this.repository.generateToken(this.request);
|
||||||
|
@ -112,7 +124,17 @@ public class CookieCsrfTokenRepositoryTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void saveTokenNull() {
|
void saveTokenSecureFlagFalseUsingCustomizer() {
|
||||||
|
this.request.setSecure(true);
|
||||||
|
this.repository.setCookieCustomizer(customizer -> customizer.secure(Boolean.FALSE));
|
||||||
|
CsrfToken token = this.repository.generateToken(this.request);
|
||||||
|
this.repository.saveToken(token, this.request, this.response);
|
||||||
|
Cookie tokenCookie = this.response.getCookie(CookieCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME);
|
||||||
|
assertThat(tokenCookie.getSecure()).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void saveTokenNull() {
|
||||||
this.request.setSecure(true);
|
this.request.setSecure(true);
|
||||||
this.repository.saveToken(null, this.request, this.response);
|
this.repository.saveToken(null, this.request, this.response);
|
||||||
Cookie tokenCookie = this.response.getCookie(CookieCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME);
|
Cookie tokenCookie = this.response.getCookie(CookieCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME);
|
||||||
|
@ -133,7 +155,16 @@ public class CookieCsrfTokenRepositoryTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void saveTokenHttpOnlyFalse() {
|
void saveTokenHttpOnlyTrueUsingCustomizer() {
|
||||||
|
this.repository.setCookieCustomizer(customizer -> customizer.httpOnly(true));
|
||||||
|
CsrfToken token = this.repository.generateToken(this.request);
|
||||||
|
this.repository.saveToken(token, this.request, this.response);
|
||||||
|
Cookie tokenCookie = this.response.getCookie(CookieCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME);
|
||||||
|
assertThat(tokenCookie.isHttpOnly()).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void saveTokenHttpOnlyFalse() {
|
||||||
this.repository.setCookieHttpOnly(false);
|
this.repository.setCookieHttpOnly(false);
|
||||||
CsrfToken token = this.repository.generateToken(this.request);
|
CsrfToken token = this.repository.generateToken(this.request);
|
||||||
this.repository.saveToken(token, this.request, this.response);
|
this.repository.saveToken(token, this.request, this.response);
|
||||||
|
@ -142,7 +173,16 @@ public class CookieCsrfTokenRepositoryTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void saveTokenWithHttpOnlyFalse() {
|
void saveTokenHttpOnlyFalseUsingCustomizer() {
|
||||||
|
this.repository.setCookieCustomizer(customizer -> customizer.httpOnly(false));
|
||||||
|
CsrfToken token = this.repository.generateToken(this.request);
|
||||||
|
this.repository.saveToken(token, this.request, this.response);
|
||||||
|
Cookie tokenCookie = this.response.getCookie(CookieCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME);
|
||||||
|
assertThat(tokenCookie.isHttpOnly()).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void saveTokenWithHttpOnlyFalse() {
|
||||||
this.repository = CookieCsrfTokenRepository.withHttpOnlyFalse();
|
this.repository = CookieCsrfTokenRepository.withHttpOnlyFalse();
|
||||||
CsrfToken token = this.repository.generateToken(this.request);
|
CsrfToken token = this.repository.generateToken(this.request);
|
||||||
this.repository.saveToken(token, this.request, this.response);
|
this.repository.saveToken(token, this.request, this.response);
|
||||||
|
@ -190,6 +230,16 @@ public class CookieCsrfTokenRepositoryTests {
|
||||||
assertThat(tokenCookie.getDomain()).isEqualTo(domainName);
|
assertThat(tokenCookie.getDomain()).isEqualTo(domainName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void saveTokenWithCookieDomainUsingCustomizer() {
|
||||||
|
String domainName = "example.com";
|
||||||
|
this.repository.setCookieCustomizer(customizer -> customizer.domain(domainName));
|
||||||
|
CsrfToken token = this.repository.generateToken(this.request);
|
||||||
|
this.repository.saveToken(token, this.request, this.response);
|
||||||
|
Cookie tokenCookie = this.response.getCookie(CookieCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME);
|
||||||
|
assertThat(tokenCookie.getDomain()).isEqualTo(domainName);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void saveTokenWithCookieMaxAge() {
|
public void saveTokenWithCookieMaxAge() {
|
||||||
int maxAge = 1200;
|
int maxAge = 1200;
|
||||||
|
@ -200,6 +250,46 @@ public class CookieCsrfTokenRepositoryTests {
|
||||||
assertThat(tokenCookie.getMaxAge()).isEqualTo(maxAge);
|
assertThat(tokenCookie.getMaxAge()).isEqualTo(maxAge);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void saveTokenWithCookieMaxAgeUsingCustomizer() {
|
||||||
|
int maxAge = 1200;
|
||||||
|
this.repository.setCookieCustomizer(customizer -> customizer.maxAge(maxAge));
|
||||||
|
CsrfToken token = this.repository.generateToken(this.request);
|
||||||
|
this.repository.saveToken(token, this.request, this.response);
|
||||||
|
Cookie tokenCookie = this.response.getCookie(CookieCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME);
|
||||||
|
assertThat(tokenCookie.getMaxAge()).isEqualTo(maxAge);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void saveTokenWithSameSiteNull() {
|
||||||
|
String sameSitePolicy = null;
|
||||||
|
this.repository.setCookieCustomizer(customizer -> customizer.sameSite(sameSitePolicy));
|
||||||
|
CsrfToken token = this.repository.generateToken(this.request);
|
||||||
|
this.repository.saveToken(token, this.request, this.response);
|
||||||
|
Cookie tokenCookie = this.response.getCookie(CookieCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME);
|
||||||
|
assertThat(((MockCookie)tokenCookie).getSameSite()).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void saveTokenWithSameSiteStrict() {
|
||||||
|
String sameSitePolicy = "Strict";
|
||||||
|
this.repository.setCookieCustomizer(customizer -> customizer.sameSite(sameSitePolicy));
|
||||||
|
CsrfToken token = this.repository.generateToken(this.request);
|
||||||
|
this.repository.saveToken(token, this.request, this.response);
|
||||||
|
Cookie tokenCookie = this.response.getCookie(CookieCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME);
|
||||||
|
assertThat(((MockCookie)tokenCookie).getSameSite()).isEqualTo(sameSitePolicy);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void saveTokenWithSameSiteLax() {
|
||||||
|
String sameSitePolicy = "Lax";
|
||||||
|
this.repository.setCookieCustomizer(customizer -> customizer.sameSite(sameSitePolicy));
|
||||||
|
CsrfToken token = this.repository.generateToken(this.request);
|
||||||
|
this.repository.saveToken(token, this.request, this.response);
|
||||||
|
Cookie tokenCookie = this.response.getCookie(CookieCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME);
|
||||||
|
assertThat(((MockCookie)tokenCookie).getSameSite()).isEqualTo(sameSitePolicy);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void loadTokenNoCookiesNull() {
|
public void loadTokenNoCookiesNull() {
|
||||||
assertThat(this.repository.loadToken(this.request)).isNull();
|
assertThat(this.repository.loadToken(this.request)).isNull();
|
||||||
|
@ -299,6 +389,28 @@ public class CookieCsrfTokenRepositoryTests {
|
||||||
assertThat(deferredCsrfToken.isGenerated()).isFalse();
|
assertThat(deferredCsrfToken.isGenerated()).isFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void cookieCustomizer() {
|
||||||
|
String domainName = "example.com";
|
||||||
|
String customPath = "/custompath";
|
||||||
|
String sameSitePolicy = "Strict";
|
||||||
|
this.repository.setCookieCustomizer(customizer -> {
|
||||||
|
customizer.domain(domainName);
|
||||||
|
customizer.secure(false);
|
||||||
|
customizer.path(customPath);
|
||||||
|
customizer.sameSite(sameSitePolicy);
|
||||||
|
});
|
||||||
|
CsrfToken token = this.repository.generateToken(this.request);
|
||||||
|
this.repository.saveToken(token, this.request, this.response);
|
||||||
|
Cookie tokenCookie = this.response.getCookie(CookieCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME);
|
||||||
|
assertThat(tokenCookie).isNotNull();
|
||||||
|
assertThat(tokenCookie.getMaxAge()).isEqualTo(-1);
|
||||||
|
assertThat(tokenCookie.getDomain()).isEqualTo(domainName);
|
||||||
|
assertThat(tokenCookie.getPath()).isEqualTo(customPath);
|
||||||
|
assertThat(tokenCookie.isHttpOnly()).isEqualTo(Boolean.TRUE);
|
||||||
|
assertThat(((MockCookie)tokenCookie).getSameSite()).isEqualTo(sameSitePolicy);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void setCookieNameNullIllegalArgumentException() {
|
public void setCookieNameNullIllegalArgumentException() {
|
||||||
assertThatIllegalArgumentException().isThrownBy(() -> this.repository.setCookieName(null));
|
assertThatIllegalArgumentException().isThrownBy(() -> this.repository.setCookieName(null));
|
||||||
|
|
|
@ -18,6 +18,7 @@ package org.springframework.security.web.server.csrf;
|
||||||
|
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
@ -35,6 +36,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||||
* @author Eric Deandrea
|
* @author Eric Deandrea
|
||||||
* @author Thomas Vitale
|
* @author Thomas Vitale
|
||||||
* @author Alonso Araya
|
* @author Alonso Araya
|
||||||
|
* @author Alex Montoya
|
||||||
* @since 5.1
|
* @since 5.1
|
||||||
*/
|
*/
|
||||||
public class CookieServerCsrfTokenRepositoryTests {
|
public class CookieServerCsrfTokenRepositoryTests {
|
||||||
|
@ -61,6 +63,8 @@ public class CookieServerCsrfTokenRepositoryTests {
|
||||||
|
|
||||||
private String expectedCookieValue = "csrfToken";
|
private String expectedCookieValue = "csrfToken";
|
||||||
|
|
||||||
|
private String expectedSameSitePolicy = null;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
this.csrfTokenRepository = new CookieServerCsrfTokenRepository();
|
this.csrfTokenRepository = new CookieServerCsrfTokenRepository();
|
||||||
|
@ -120,6 +124,12 @@ public class CookieServerCsrfTokenRepositoryTests {
|
||||||
saveAndAssertExpectedValues(createToken());
|
saveAndAssertExpectedValues(createToken());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void saveTokenWhenSameSiteThenCookieSameSite() {
|
||||||
|
setExpectedSameSitePolicy("Lax");
|
||||||
|
saveAndAssertExpectedValues(createToken());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void saveTokenWhenCustomPropertiesThenCustomProperties() {
|
public void saveTokenWhenCustomPropertiesThenCustomProperties() {
|
||||||
setExpectedDomain("spring.io");
|
setExpectedDomain("spring.io");
|
||||||
|
@ -127,12 +137,48 @@ public class CookieServerCsrfTokenRepositoryTests {
|
||||||
setExpectedPath("/some/path");
|
setExpectedPath("/some/path");
|
||||||
setExpectedHeaderName("headerName");
|
setExpectedHeaderName("headerName");
|
||||||
setExpectedParameterName("paramName");
|
setExpectedParameterName("paramName");
|
||||||
|
setExpectedSameSitePolicy("Strict");
|
||||||
setExpectedCookieMaxAge(3600);
|
setExpectedCookieMaxAge(3600);
|
||||||
saveAndAssertExpectedValues(createToken());
|
saveAndAssertExpectedValues(createToken());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void saveTokenWhenSslInfoPresentThenSecure() {
|
void saveTokenWhenCustomPropertiesThenCustomPropertiesUsingCustomizer() {
|
||||||
|
String expectedDomain = "spring.io";
|
||||||
|
int expectedMaxAge = 3600;
|
||||||
|
String expectedPath = "/some/path";
|
||||||
|
String expectedSameSite = "Strict";
|
||||||
|
|
||||||
|
setExpectedCookieName("csrfCookie");
|
||||||
|
|
||||||
|
setExpectedHeaderName("headerName");
|
||||||
|
setExpectedParameterName("paramName");
|
||||||
|
|
||||||
|
CsrfToken token = createToken();
|
||||||
|
|
||||||
|
this.csrfTokenRepository.setCookieCustomizer(customizer -> {
|
||||||
|
customizer.domain(expectedDomain);
|
||||||
|
customizer.maxAge(expectedMaxAge);
|
||||||
|
customizer.path(expectedPath);
|
||||||
|
customizer.sameSite(expectedSameSite);
|
||||||
|
});
|
||||||
|
|
||||||
|
MockServerWebExchange exchange = MockServerWebExchange.from(this.request);
|
||||||
|
this.csrfTokenRepository.saveToken(exchange, token).block();
|
||||||
|
ResponseCookie cookie = exchange.getResponse().getCookies().getFirst(this.expectedCookieName);
|
||||||
|
assertThat(cookie).isNotNull();
|
||||||
|
assertThat(cookie.getMaxAge()).isEqualTo(Duration.of(expectedMaxAge, ChronoUnit.SECONDS));
|
||||||
|
assertThat(cookie.getDomain()).isEqualTo(expectedDomain);
|
||||||
|
assertThat(cookie.getPath()).isEqualTo(expectedPath);
|
||||||
|
assertThat(cookie.getSameSite()).isEqualTo(expectedSameSite);
|
||||||
|
assertThat(cookie.isSecure()).isEqualTo(this.expectedSecure);
|
||||||
|
assertThat(cookie.isHttpOnly()).isEqualTo(this.expectedHttpOnly);
|
||||||
|
assertThat(cookie.getName()).isEqualTo(this.expectedCookieName);
|
||||||
|
assertThat(cookie.getValue()).isEqualTo(this.expectedCookieValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void saveTokenWhenSslInfoPresentThenSecure() {
|
||||||
this.request.sslInfo(new MockSslInfo());
|
this.request.sslInfo(new MockSslInfo());
|
||||||
MockServerWebExchange exchange = MockServerWebExchange.from(this.request);
|
MockServerWebExchange exchange = MockServerWebExchange.from(this.request);
|
||||||
this.csrfTokenRepository.saveToken(exchange, createToken()).block();
|
this.csrfTokenRepository.saveToken(exchange, createToken()).block();
|
||||||
|
@ -160,6 +206,16 @@ public class CookieServerCsrfTokenRepositoryTests {
|
||||||
assertThat(cookie.isSecure()).isTrue();
|
assertThat(cookie.isSecure()).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void saveTokenWhenSecureFlagTrueThenSecureUsingCustomizer() {
|
||||||
|
MockServerWebExchange exchange = MockServerWebExchange.from(this.request);
|
||||||
|
this.csrfTokenRepository.setCookieCustomizer(customizer -> customizer.secure(true));
|
||||||
|
this.csrfTokenRepository.saveToken(exchange, createToken()).block();
|
||||||
|
ResponseCookie cookie = exchange.getResponse().getCookies().getFirst(this.expectedCookieName);
|
||||||
|
assertThat(cookie).isNotNull();
|
||||||
|
assertThat(cookie.isSecure()).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void saveTokenWhenSecureFlagFalseThenNotSecure() {
|
public void saveTokenWhenSecureFlagFalseThenNotSecure() {
|
||||||
MockServerWebExchange exchange = MockServerWebExchange.from(this.request);
|
MockServerWebExchange exchange = MockServerWebExchange.from(this.request);
|
||||||
|
@ -170,6 +226,16 @@ public class CookieServerCsrfTokenRepositoryTests {
|
||||||
assertThat(cookie.isSecure()).isFalse();
|
assertThat(cookie.isSecure()).isFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void saveTokenWhenSecureFlagFalseThenNotSecureUsingCustomizer() {
|
||||||
|
MockServerWebExchange exchange = MockServerWebExchange.from(this.request);
|
||||||
|
this.csrfTokenRepository.setCookieCustomizer(customizer -> customizer.secure(false));
|
||||||
|
this.csrfTokenRepository.saveToken(exchange, createToken()).block();
|
||||||
|
ResponseCookie cookie = exchange.getResponse().getCookies().getFirst(this.expectedCookieName);
|
||||||
|
assertThat(cookie).isNotNull();
|
||||||
|
assertThat(cookie.isSecure()).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void saveTokenWhenSecureFlagFalseAndSslInfoThenNotSecure() {
|
public void saveTokenWhenSecureFlagFalseAndSslInfoThenNotSecure() {
|
||||||
MockServerWebExchange exchange = MockServerWebExchange.from(this.request);
|
MockServerWebExchange exchange = MockServerWebExchange.from(this.request);
|
||||||
|
@ -181,6 +247,17 @@ public class CookieServerCsrfTokenRepositoryTests {
|
||||||
assertThat(cookie.isSecure()).isFalse();
|
assertThat(cookie.isSecure()).isFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void saveTokenWhenSecureFlagFalseAndSslInfoThenNotSecureUsingCustomizer() {
|
||||||
|
MockServerWebExchange exchange = MockServerWebExchange.from(this.request);
|
||||||
|
this.request.sslInfo(new MockSslInfo());
|
||||||
|
this.csrfTokenRepository.setCookieCustomizer(customizer -> customizer.secure(false));
|
||||||
|
this.csrfTokenRepository.saveToken(exchange, createToken()).block();
|
||||||
|
ResponseCookie cookie = exchange.getResponse().getCookies().getFirst(this.expectedCookieName);
|
||||||
|
assertThat(cookie).isNotNull();
|
||||||
|
assertThat(cookie.isSecure()).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void loadTokenWhenCookieExistThenTokenFound() {
|
public void loadTokenWhenCookieExistThenTokenFound() {
|
||||||
loadAndAssertExpectedValues();
|
loadAndAssertExpectedValues();
|
||||||
|
@ -248,6 +325,11 @@ public class CookieServerCsrfTokenRepositoryTests {
|
||||||
this.expectedMaxAge = Duration.ofSeconds(expectedCookieMaxAge);
|
this.expectedMaxAge = Duration.ofSeconds(expectedCookieMaxAge);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setExpectedSameSitePolicy(String sameSitePolicy){
|
||||||
|
this.csrfTokenRepository.setCookieCustomizer(customizer -> customizer.sameSite(sameSitePolicy));
|
||||||
|
this.expectedSameSitePolicy = sameSitePolicy;
|
||||||
|
}
|
||||||
|
|
||||||
private void setExpectedCookieValue(String expectedCookieValue) {
|
private void setExpectedCookieValue(String expectedCookieValue) {
|
||||||
this.expectedCookieValue = expectedCookieValue;
|
this.expectedCookieValue = expectedCookieValue;
|
||||||
}
|
}
|
||||||
|
@ -284,6 +366,7 @@ public class CookieServerCsrfTokenRepositoryTests {
|
||||||
assertThat(cookie.isHttpOnly()).isEqualTo(this.expectedHttpOnly);
|
assertThat(cookie.isHttpOnly()).isEqualTo(this.expectedHttpOnly);
|
||||||
assertThat(cookie.getName()).isEqualTo(this.expectedCookieName);
|
assertThat(cookie.getName()).isEqualTo(this.expectedCookieName);
|
||||||
assertThat(cookie.getValue()).isEqualTo(this.expectedCookieValue);
|
assertThat(cookie.getValue()).isEqualTo(this.expectedCookieValue);
|
||||||
|
assertThat(cookie.getSameSite()).isEqualTo(this.expectedSameSitePolicy);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void generateTokenAndAssertExpectedValues() {
|
private void generateTokenAndAssertExpectedValues() {
|
||||||
|
|
Loading…
Reference in New Issue