Update CSRF settings to align with Spring Security 6.

This commit is contained in:
Tim Donohue
2024-03-22 15:18:30 -05:00
parent f422e61a75
commit 1648d61cfa
6 changed files with 508 additions and 201 deletions

View File

@@ -9,11 +9,17 @@ package org.dspace.app.rest.security;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.session.SessionAuthenticationException;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
import org.springframework.security.web.csrf.CsrfAuthenticationStrategy;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.csrf.CsrfTokenRepository;
import org.springframework.security.web.csrf.CsrfTokenRequestHandler;
import org.springframework.security.web.csrf.DeferredCsrfToken;
import org.springframework.security.web.csrf.XorCsrfTokenRequestAttributeHandler;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -24,29 +30,41 @@ import org.springframework.util.StringUtils;
* successfully or unsuccessfully). This ensures that the Token is not changed on every request (since we are stateless
* every request creates a new Authentication object).
* <P>
* Based on Spring Security's CsrfAuthenticationStrategy:
* https://github.com/spring-projects/spring-security/blob/5.2.x/web/src/main/java/org/springframework/security/web/csrf/CsrfAuthenticationStrategy.java
* This is essentially a customization of Spring Security's CsrfAuthenticationStrategy:
* https://github.com/spring-projects/spring-security/blob/6.2.x/web/src/main/java/org/springframework/security/web/csrf/CsrfAuthenticationStrategy.java
*/
public class DSpaceCsrfAuthenticationStrategy implements SessionAuthenticationStrategy {
private final CsrfTokenRepository csrfTokenRepository;
private final Log logger = LogFactory.getLog(getClass());
private final CsrfTokenRepository tokenRepository;
private CsrfTokenRequestHandler requestHandler = new XorCsrfTokenRequestAttributeHandler();
/**
* Creates a new instance
* @param csrfTokenRepository the {@link CsrfTokenRepository} to use
* @param tokenRepository the {@link CsrfTokenRepository} to use
*/
public DSpaceCsrfAuthenticationStrategy(CsrfTokenRepository csrfTokenRepository) {
Assert.notNull(csrfTokenRepository, "csrfTokenRepository cannot be null");
this.csrfTokenRepository = csrfTokenRepository;
public DSpaceCsrfAuthenticationStrategy(CsrfTokenRepository tokenRepository) {
Assert.notNull(tokenRepository, "tokenRepository cannot be null");
this.tokenRepository = tokenRepository;
}
/**
* Method is copied from {@link CsrfAuthenticationStrategy#setRequestHandler(CsrfTokenRequestHandler)}
*/
public void setRequestHandler(CsrfTokenRequestHandler requestHandler) {
Assert.notNull(requestHandler, "requestHandler cannot be null");
this.requestHandler = requestHandler;
}
/**
* This method is triggered anytime a new Authentication occurs. As DSpace uses Stateless authentication,
* this method is triggered on _every request_ after an initial login occurs. This is because the Spring Security
* Authentication object is recreated on every request.
* 'Authentication' object is recreated on every request.
* <P>
* Therefore, for DSpace, we've customized this method to ensure a new CSRF Token is NOT generated each time a new
* Authentication object is created -- doing so causes the CSRF Token to change with every request. Instead, we
* Authentication object is created -- as doing so causes the CSRF Token to change with every request. Instead, we
* check to see if the client also passed a CSRF token via a querystring parameter (i.e. "_csrf"). If so, this means
* the client has sent the token in a less secure manner & it must then be regenerated.
* <P>
@@ -57,8 +75,10 @@ public class DSpaceCsrfAuthenticationStrategy implements SessionAuthenticationSt
HttpServletRequest request, HttpServletResponse response)
throws SessionAuthenticationException {
// Check if token returned in server-side cookie
CsrfToken token = this.csrfTokenRepository.loadToken(request);
CsrfToken token = this.tokenRepository.loadToken(request);
// For DSpace, this will only be null if we are forcing CSRF token regeneration (e.g. on initial login)
boolean containsToken = token != null;
@@ -69,18 +89,28 @@ public class DSpaceCsrfAuthenticationStrategy implements SessionAuthenticationSt
// If token exists was sent in a parameter, then we need to reset our token
// (as sending token in a param is insecure)
if (containsParameter) {
resetCSRFToken(request, response);
}
}
}
/**
* A custom utility method to force Spring Security to reset the CSRF token. This is used by DSpace to reset
* the token whenever the CSRF token is passed insecurely (as a request param, see onAuthentication() above)
* or on logout (see JWTTokenRestAuthenticationServiceImpl)
* @param request current HTTP request
* @param response current HTTP response
* @see org.dspace.app.rest.security.jwt.JWTTokenRestAuthenticationServiceImpl
*/
public void resetCSRFToken(HttpServletRequest request, HttpServletResponse response) {
// Note: We first set the token to null & then set a new one. This results in 2 cookies sent,
// the first being empty and the second having the new token.
// This behavior is borrowed from Spring Security's CsrfAuthenticationStrategy, see
// https://github.com/spring-projects/spring-security/blob/5.4.x/web/src/main/java/org/springframework/security/web/csrf/CsrfAuthenticationStrategy.java
this.csrfTokenRepository.saveToken(null, request, response);
CsrfToken newToken = this.csrfTokenRepository.generateToken(request);
this.csrfTokenRepository.saveToken(newToken, request, response);
request.setAttribute(CsrfToken.class.getName(), newToken);
request.setAttribute(newToken.getParameterName(), newToken);
}
}
// https://github.com/spring-projects/spring-security/blob/6.2.x/web/src/main/java/org/springframework/security/web/csrf/CsrfAuthenticationStrategy.java
this.tokenRepository.saveToken(null, request, response);
DeferredCsrfToken deferredCsrfToken = this.tokenRepository.loadDeferredToken(request, response);
this.requestHandler.handle(request, response, deferredCsrfToken::get);
this.logger.debug("Replaced CSRF Token");
}
}

View File

@@ -8,12 +8,13 @@
package org.dspace.app.rest.security;
import java.util.UUID;
import java.util.function.Consumer;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.ws.rs.core.HttpHeaders;
import org.springframework.http.ResponseCookie;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.csrf.CsrfTokenRepository;
import org.springframework.security.web.csrf.DefaultCsrfToken;
@@ -25,9 +26,10 @@ import org.springframework.web.util.WebUtils;
* This is a custom Spring Security CsrfTokenRepository which supports *cross-domain* CSRF protection (allowing the
* client and backend to be on different domains). It's inspired by https://stackoverflow.com/a/33175322
* <P>
* This also borrows heavily from Spring Security's CookieCsrfTokenRepository:
* https://github.com/spring-projects/spring-security/blob/5.2.x/web/src/main/java/org/springframework/security/web/csrf/CookieCsrfTokenRepository.java
*
* This is essentially a customization of Spring Security's CookieCsrfTokenRepository:
* https://github.com/spring-projects/spring-security/blob/6.2.x/web/src/main/java/org/springframework/security/web/csrf/CookieCsrfTokenRepository.java
* However, as that class is "final" we aannot override it directly.
* <P>
* How it works:
*
* 1. Backend generates XSRF token & stores in a *server-side* cookie named DSPACE-XSRF-COOKIE. By default, this cookie
@@ -59,6 +61,9 @@ public class DSpaceCsrfTokenRepository 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;
@@ -71,73 +76,93 @@ public class DSpaceCsrfTokenRepository implements CsrfTokenRepository {
private String cookieDomain;
private Boolean secure;
private int cookieMaxAge = -1;
private Consumer<ResponseCookie.ResponseCookieBuilder> cookieCustomizer = (builder) -> {
};
public DSpaceCsrfTokenRepository() {
}
@Override
public CsrfToken generateToken(HttpServletRequest request) {
return new DefaultCsrfToken(this.headerName, this.parameterName,
createNewToken());
/**
* Method is copied from {@link CookieCsrfTokenRepository#setCookieCustomizer(Consumer)}
*/
public void setCookieCustomizer(Consumer<ResponseCookie.ResponseCookieBuilder> cookieCustomizer) {
Assert.notNull(cookieCustomizer, "cookieCustomizer must not be null");
this.cookieCustomizer = cookieCustomizer;
}
/**
* This method has been modified for DSpace.
* Method is copied from {@link CookieCsrfTokenRepository#generateToken(HttpServletRequest)}
*/
@Override
public CsrfToken generateToken(HttpServletRequest request) {
return new DefaultCsrfToken(this.headerName, this.parameterName, createNewToken());
}
/**
* This method has been modified for DSpace. It borrows MOST of the logic from
* {@link CookieCsrfTokenRepository#saveToken(CsrfToken, HttpServletRequest, HttpServletResponse)}
* <P>
* It now uses ResponseCookie to build the cookie, so that the "SameSite" attribute can be applied.
* It applies a "SameSite" attribute to every cookie by default.
* <P>
* It also sends the token (if not empty) in both the cookie and the custom "DSPACE-XSRF-TOKEN" header
* It also sends the token (if not empty) back in BOTH the cookie and the custom "DSPACE-XSRF-TOKEN" header.
* By default, Spring Security will only send the token back in the cookie.
* @param token current token
* @param request current request
* @param response current response
*/
@Override
public void saveToken(CsrfToken token, HttpServletRequest request,
HttpServletResponse response) {
String tokenValue = token == null ? "" : token.getToken();
Cookie cookie = new Cookie(this.cookieName, tokenValue);
cookie.setSecure(request.isSecure());
if (this.cookiePath != null && !this.cookiePath.isEmpty()) {
cookie.setPath(this.cookiePath);
} else {
cookie.setPath(this.getRequestContext(request));
}
if (token == null) {
cookie.setMaxAge(0);
} else {
cookie.setMaxAge(-1);
}
cookie.setHttpOnly(cookieHttpOnly);
if (this.cookieDomain != null && !this.cookieDomain.isEmpty()) {
cookie.setDomain(this.cookieDomain);
}
public void saveToken(CsrfToken token, HttpServletRequest request, HttpServletResponse response) {
String tokenValue = (token != null) ? token.getToken() : "";
// Custom: Turn the above Cookie into a ResponseCookie so that we can set "SameSite" attribute
// If client is on a different domain than the backend, then Cookie MUST use "SameSite=None" and "Secure".
// Most modern browsers will block it otherwise.
// TODO: Make SameSite configurable? "Lax" cookies are more secure, but require client & backend on same domain.
String sameSite = "None";
if (!cookie.getSecure()) {
sameSite = "Lax";
}
ResponseCookie responseCookie = ResponseCookie.from(cookie.getName(), cookie.getValue())
.path(cookie.getPath()).maxAge(cookie.getMaxAge())
.domain(cookie.getDomain()).httpOnly(cookie.isHttpOnly())
.secure(cookie.getSecure()).sameSite(sameSite).build();
ResponseCookie.ResponseCookieBuilder cookieBuilder =
ResponseCookie.from(this.cookieName, tokenValue)
.secure((this.secure != null) ? this.secure : request.isSecure())
.path(StringUtils.hasLength(this.cookiePath) ?
this.cookiePath : this.getRequestContext(request))
.maxAge((token != null) ? this.cookieMaxAge : 0)
.httpOnly(this.cookieHttpOnly)
.domain(this.cookieDomain)
// Custom for DSpace: If client is on a different domain than the backend, then Cookie MUST
// use "SameSite=None" and "Secure". Most modern browsers will block it otherwise.
// TODO: Make SameSite configurable? "Lax" cookies are more secure, but require client &
// backend on same domain.
.sameSite(request.isSecure() ? "None" : "Lax");;
// Write the ResponseCookie to the Set-Cookie header
// This cookie is only used by the backend & not needed by client
response.addHeader(HttpHeaders.SET_COOKIE, responseCookie.toString());
this.cookieCustomizer.accept(cookieBuilder);
// Send custom header to client with token (only if token not empty)
// We send our token via a custom header because client can be on a different domain.
// Custom for DSpace: also send custom header to client with token.
// We send our token via a custom header because client may be on a different domain.
// Cookies cannot be reliably sent cross-domain.
if (StringUtils.hasLength(tokenValue)) {
response.setHeader("DSPACE-XSRF-TOKEN", tokenValue);
}
Cookie cookie = mapToCookie(cookieBuilder.build());
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);
}
}
/**
* Method is copied from {@link CookieCsrfTokenRepository#loadToken(HttpServletRequest)}
*/
@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;
@@ -146,104 +171,124 @@ public class DSpaceCsrfTokenRepository implements CsrfTokenRepository {
if (!StringUtils.hasLength(token)) {
return null;
}
return new DefaultCsrfToken(this.headerName, this.parameterName, token);
}
/**
* Sets the name of the HTTP request parameter that should be used to provide a token.
*
* @param parameterName the name of the HTTP request parameter that should be used to
* provide a token
* Method is copied from {@link CookieCsrfTokenRepository#setParameterName(String)}
*/
public void setParameterName(String parameterName) {
Assert.notNull(parameterName, "parameterName is not null");
Assert.notNull(parameterName, "parameterName cannot be null");
this.parameterName = parameterName;
}
/**
* Sets the name of the HTTP header that should be used to provide the token.
*
* @param headerName the name of the HTTP header that should be used to provide the
* token
* Method is copied from {@link CookieCsrfTokenRepository#setHeaderName(String)}
*/
public void setHeaderName(String headerName) {
Assert.notNull(headerName, "headerName is not null");
Assert.notNull(headerName, "headerName cannot be null");
this.headerName = headerName;
}
/**
* Sets the name of the cookie that the expected CSRF token is saved to and read from.
*
* @param cookieName the name of the cookie that the expected CSRF token is saved to
* and read from
* Method is copied from {@link CookieCsrfTokenRepository#setCookieName(String)}
*/
public void setCookieName(String cookieName) {
Assert.notNull(cookieName, "cookieName is not null");
Assert.notNull(cookieName, "cookieName cannot be null");
this.cookieName = cookieName;
}
/**
* Sets the HttpOnly attribute on the cookie containing the CSRF token.
* Defaults to <code>true</code>.
*
* @param cookieHttpOnly <code>true</code> sets the HttpOnly attribute, <code>false</code> does not set it
* Method is copied from {@link CookieCsrfTokenRepository#setCookieHttpOnly(boolean)}
* @deprecated Use {@link #setCookieCustomizer(Consumer)} instead.
*/
@Deprecated
public void setCookieHttpOnly(boolean cookieHttpOnly) {
this.cookieHttpOnly = cookieHttpOnly;
}
/**
* Method is copied from {@link CookieCsrfTokenRepository}
*/
private String getRequestContext(HttpServletRequest request) {
String contextPath = request.getContextPath();
return contextPath.length() > 0 ? contextPath : "/";
return (contextPath.length() > 0) ? contextPath : "/";
}
/**
* Factory method to conveniently create an instance that has
* {@link #setCookieHttpOnly(boolean)} set to false.
*
* @return an instance of CookieCsrfTokenRepository with
* {@link #setCookieHttpOnly(boolean)} set to false
* Method is copied from {@link CookieCsrfTokenRepository}
* (and only modified to return the DSpaceCsrfTokenRepository instead)
*/
public static DSpaceCsrfTokenRepository withHttpOnlyFalse() {
DSpaceCsrfTokenRepository result = new DSpaceCsrfTokenRepository();
result.setCookieHttpOnly(false);
result.cookieHttpOnly = false;
return result;
}
/**
* Method is copied from {@link CookieCsrfTokenRepository}
*/
private String createNewToken() {
return UUID.randomUUID().toString();
}
/**
* Set the path that the Cookie will be created with. This will override the default functionality which uses the
* request context as the path.
*
* @param path the path to use
* Method is copied from {@link CookieCsrfTokenRepository}
*/
private Cookie mapToCookie(ResponseCookie responseCookie) {
Cookie cookie = new Cookie(responseCookie.getName(), responseCookie.getValue());
cookie.setSecure(responseCookie.isSecure());
cookie.setPath(responseCookie.getPath());
cookie.setMaxAge((int) responseCookie.getMaxAge().getSeconds());
cookie.setHttpOnly(responseCookie.isHttpOnly());
if (StringUtils.hasLength(responseCookie.getDomain())) {
cookie.setDomain(responseCookie.getDomain());
}
if (StringUtils.hasText(responseCookie.getSameSite())) {
cookie.setAttribute("SameSite", responseCookie.getSameSite());
}
return cookie;
}
/**
* Method is copied from {@link CookieCsrfTokenRepository#setCookiePath(String)}
*/
public void setCookiePath(String path) {
this.cookiePath = path;
}
/**
* Get the path that the CSRF cookie will be set to.
*
* @return the path to be used.
* Method is copied from {@link CookieCsrfTokenRepository#getCookiePath()}
*/
public String getCookiePath() {
return this.cookiePath;
}
/**
* Sets the domain of the cookie that the expected CSRF token is saved to and read from.
*
* @since 5.2
* @param cookieDomain the domain of the cookie that the expected CSRF token is saved to
* and read from
* Method is copied from {@link CookieCsrfTokenRepository#setCookieDomain(String)}
* @deprecated Use {@link #setCookieCustomizer(Consumer)} instead.
*/
@Deprecated
public void setCookieDomain(String cookieDomain) {
this.cookieDomain = cookieDomain;
}
/**
* Method is copied from {@link CookieCsrfTokenRepository#setSecure(Boolean)}
* @deprecated Use {@link #setCookieCustomizer(Consumer)} instead.
*/
@Deprecated
public void setSecure(Boolean secure) {
this.secure = secure;
}
/**
* Method is copied from {@link CookieCsrfTokenRepository#setCookieMaxAge(int)}
* @deprecated Use {@link #setCookieCustomizer(Consumer)} instead.
*/
@Deprecated
public void setCookieMaxAge(int cookieMaxAge) {
Assert.isTrue(cookieMaxAge != 0, "cookieMaxAge cannot be zero");
this.cookieMaxAge = cookieMaxAge;
}
}

View File

@@ -0,0 +1,66 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.security;
import java.util.function.Supplier;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler;
import org.springframework.security.web.csrf.CsrfTokenRequestHandler;
import org.springframework.security.web.csrf.XorCsrfTokenRequestAttributeHandler;
import org.springframework.util.StringUtils;
/**
* A custom Spring Security CsrfTokenRequestAttributeHandler which uses the Spring Security BREACH protection
* (provided by XorCsrfTokenRequestAttributeHandler) *only* when the CSRF token is sent as a "_csrf" request parameter.
* In all other scenarios, the CsrfTokenRequestAttributeHandler is used instead.
* <P>
* NOTE: The DSpace UI always sends the CSRF Token as a request header. It does NOT send it as a "_csrf" request
* paramter. So, this BREACH protection would ONLY be triggered for custom clients (not the DSpace UI).
* Therefore, if using this custom class becomes problematic, we could revert to using the default
* CsrfTokenRequestAttributeHandler without any negative impact on the DSpace UI.
* <P>
* This code is copied from the example "SpaCsrfTokenRequestHandler" (for single page applications) from the Spring
* Security docs at
* https://docs.spring.io/spring-security/reference/servlet/exploits/csrf.html#csrf-integration-javascript-spa-configuration
*/
public final class DSpaceCsrfTokenRequestHandler extends CsrfTokenRequestAttributeHandler {
private final CsrfTokenRequestHandler delegate = new XorCsrfTokenRequestAttributeHandler();
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, Supplier<CsrfToken> csrfToken) {
/*
* Always use XorCsrfTokenRequestAttributeHandler to provide BREACH protection of
* the CsrfToken when it is rendered in the response body.
* NOTE: This should never occur from the DSpace UI, so it is only applicable for custom clients.
*/
this.delegate.handle(request, response, csrfToken);
}
@Override
public String resolveCsrfTokenValue(HttpServletRequest request, CsrfToken csrfToken) {
/*
* If the request contains a request header, use CsrfTokenRequestAttributeHandler
* to resolve the CsrfToken. This applies to the DSpace UI which always includes
* the raw CsrfToken in an HTTP Header.
*/
if (StringUtils.hasText(request.getHeader(csrfToken.getHeaderName()))) {
return super.resolveCsrfTokenValue(request, csrfToken);
}
/*
* In all other cases (e.g. if the request contains a request parameter), use
* XorCsrfTokenRequestAttributeHandler to resolve the CsrfToken. This applies
* when a server-side rendered form includes the _csrf request parameter as a
* hidden input.
* NOTE: This should never occur from the DSpace UI, so it is only applicable for custom clients.
*/
return this.delegate.resolveCsrfTokenValue(request, csrfToken);
}
}

View File

@@ -28,7 +28,6 @@ import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
import org.springframework.security.web.csrf.CsrfTokenRepository;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
@@ -112,8 +111,8 @@ public class WebSecurityConfiguration {
// While we primarily use JWT in headers, CSRF protection is needed because we also support JWT via Cookies
.csrf((csrf) -> csrf
.csrfTokenRepository(this.csrfTokenRepository())
.sessionAuthenticationStrategy(this.sessionAuthenticationStrategy())
)
.sessionAuthenticationStrategy(this.dSpaceCsrfAuthenticationStrategy())
.csrfTokenRequestHandler(new DSpaceCsrfTokenRequestHandler()))
.exceptionHandling((exceptionHandling) -> exceptionHandling
// Return 401 on authorization failures with a correct WWWW-Authenticate header
.authenticationEntryPoint(new DSpace401AuthenticationEntryPoint(restAuthenticationService))
@@ -187,8 +186,13 @@ public class WebSecurityConfiguration {
/**
* Returns a custom DSpaceCsrfAuthenticationStrategy, which ensures that (after authenticating) the CSRF token
* is only refreshed when it is used (or attempted to be used) by the client.
*
* This is defined as a bean so that it can also be used in other code to reset CSRF Tokens, see
* JWTTokenRestAuthenticationServiceImpl
*/
private SessionAuthenticationStrategy sessionAuthenticationStrategy() {
@Lazy
@Bean
public DSpaceCsrfAuthenticationStrategy dSpaceCsrfAuthenticationStrategy() {
return new DSpaceCsrfAuthenticationStrategy(csrfTokenRepository());
}

View File

@@ -22,6 +22,7 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dspace.app.rest.model.wrapper.AuthenticationToken;
import org.dspace.app.rest.security.DSpaceAuthentication;
import org.dspace.app.rest.security.DSpaceCsrfAuthenticationStrategy;
import org.dspace.app.rest.security.RestAuthenticationService;
import org.dspace.app.rest.utils.ContextUtil;
import org.dspace.authenticate.AuthenticationMethod;
@@ -33,8 +34,6 @@ import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.ResponseCookie;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.csrf.CsrfTokenRepository;
import org.springframework.stereotype.Component;
/**
@@ -67,7 +66,7 @@ public class JWTTokenRestAuthenticationServiceImpl implements RestAuthentication
@Lazy
@Autowired
private CsrfTokenRepository csrfTokenRepository;
private DSpaceCsrfAuthenticationStrategy dspaceCsrfAuthenticationStrategy;
@Override
public void afterPropertiesSet() throws Exception {
@@ -332,11 +331,8 @@ public class JWTTokenRestAuthenticationServiceImpl implements RestAuthentication
* @param response current response
*/
private void resetCSRFToken(HttpServletRequest request, HttpServletResponse response) {
// Remove current CSRF token & generate a new one
// We do this as we want the token to change anytime you login or logout
csrfTokenRepository.saveToken(null, request, response);
CsrfToken newToken = csrfTokenRepository.generateToken(request);
csrfTokenRepository.saveToken(newToken, request, response);
// Use our custom CsrfAuthenticationStrategy class to force reset the CSRF token in Spring Security
dspaceCsrfAuthenticationStrategy.resetCSRFToken(request, response);
}
}

View File

@@ -8,8 +8,10 @@
package org.dspace.app.rest.security;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import jakarta.servlet.http.Cookie;
import jakarta.ws.rs.core.HttpHeaders;
@@ -20,10 +22,11 @@ import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.csrf.DeferredCsrfToken;
/**
* This is almost an exact copy of Spring Security's CookieCsrfTokenRepositoryTests
* https://github.com/spring-projects/spring-security/blob/5.2.x/web/src/test/java/org/springframework/security/web/csrf/CookieCsrfTokenRepositoryTests.java
* This is almost an exact copy of Spring Security's DSpaceCsrfTokenRepositoryTests
* https://github.com/spring-projects/spring-security/blob/6.2.x/web/src/test/java/org/springframework/security/web/csrf/CookieCsrfTokenRepositoryTests.java
*
* The only modifications are:
* - Updating these tests to use our custom DSpaceCsrfTokenRepository
@@ -46,12 +49,9 @@ public class DSpaceCsrfTokenRepositoryTest {
@Test
public void generateToken() {
CsrfToken generateToken = this.repository.generateToken(this.request);
assertThat(generateToken).isNotNull();
assertThat(generateToken.getHeaderName())
.isEqualTo(DSpaceCsrfTokenRepository.DEFAULT_CSRF_HEADER_NAME);
assertThat(generateToken.getParameterName())
.isEqualTo(DSpaceCsrfTokenRepository.DEFAULT_CSRF_PARAMETER_NAME);
assertThat(generateToken.getHeaderName()).isEqualTo(DSpaceCsrfTokenRepository.DEFAULT_CSRF_HEADER_NAME);
assertThat(generateToken.getParameterName()).isEqualTo(DSpaceCsrfTokenRepository.DEFAULT_CSRF_PARAMETER_NAME);
assertThat(generateToken.getToken()).isNotEmpty();
}
@@ -61,9 +61,7 @@ public class DSpaceCsrfTokenRepositoryTest {
String parameterName = "paramName";
this.repository.setHeaderName(headerName);
this.repository.setParameterName(parameterName);
CsrfToken generateToken = this.repository.generateToken(this.request);
assertThat(generateToken).isNotNull();
assertThat(generateToken.getHeaderName()).isEqualTo(headerName);
assertThat(generateToken.getParameterName()).isEqualTo(parameterName);
@@ -74,17 +72,21 @@ public class DSpaceCsrfTokenRepositoryTest {
public void saveToken() {
CsrfToken token = this.repository.generateToken(this.request);
this.repository.saveToken(token, this.request, this.response);
Cookie tokenCookie = this.response
.getCookie(DSpaceCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME);
Cookie tokenCookie = this.response.getCookie(DSpaceCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME);
assertThat(tokenCookie.getMaxAge()).isEqualTo(-1);
assertThat(tokenCookie.getName())
.isEqualTo(DSpaceCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME);
assertThat(tokenCookie.getName()).isEqualTo(DSpaceCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME);
assertThat(tokenCookie.getPath()).isEqualTo(this.request.getContextPath());
assertThat(tokenCookie.getSecure()).isEqualTo(this.request.isSecure());
assertThat(tokenCookie.getValue()).isEqualTo(token.getToken());
assertThat(tokenCookie.isHttpOnly()).isEqualTo(true);
assertThat(tokenCookie.isHttpOnly()).isTrue();
}
@Test
public void saveTokenShouldUseResponseAddCookie() {
CsrfToken token = this.repository.generateToken(this.request);
MockHttpServletResponse spyResponse = spy(this.response);
this.repository.saveToken(token, this.request, spyResponse);
verify(spyResponse).addCookie(any(Cookie.class));
}
@Test
@@ -92,30 +94,73 @@ public class DSpaceCsrfTokenRepositoryTest {
this.request.setSecure(true);
CsrfToken token = this.repository.generateToken(this.request);
this.repository.saveToken(token, this.request, this.response);
Cookie tokenCookie = this.response
.getCookie(DSpaceCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME);
Cookie tokenCookie = this.response.getCookie(DSpaceCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME);
assertThat(tokenCookie.getSecure()).isTrue();
// DSpace Custom assert to verify SameSite attribute is set
// The Cookie class doesn't yet support SameSite, so we have to re-read
// the cookie from our headers, and check it.
List<String> headers = this.response.getHeaders(HttpHeaders.SET_COOKIE);
assertThat(headers.size()).isEqualTo(1);
assertThat(headers.get(0)).containsIgnoringCase("SameSite=None");
// DSpace Custom assert to verify SameSite attribute is "None" when cookie is secure
assertThat(tokenCookie.getAttribute("SameSite")).containsIgnoringCase("None");
}
// Custom test for DSpace to verify behavior for non-secure requests
@Test
public void saveTokenNotSecure() {
this.request.setSecure(false);
CsrfToken token = this.repository.generateToken(this.request);
this.repository.saveToken(token, this.request, this.response);
Cookie tokenCookie = this.response.getCookie(DSpaceCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME);
assertThat(tokenCookie.getSecure()).isFalse();
// DSpace Custom assert to verify SameSite attribute is "Lax" when cookie is NOT secure
assertThat(tokenCookie.getAttribute("SameSite")).containsIgnoringCase("Lax");
}
@Test
public void saveTokenSecureFlagTrue() {
this.request.setSecure(false);
this.repository.setSecure(Boolean.TRUE);
CsrfToken token = this.repository.generateToken(this.request);
this.repository.saveToken(token, this.request, this.response);
Cookie tokenCookie = this.response.getCookie(DSpaceCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME);
assertThat(tokenCookie.getSecure()).isTrue();
}
@Test
public 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(DSpaceCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME);
assertThat(tokenCookie.getSecure()).isTrue();
}
@Test
public void saveTokenSecureFlagFalse() {
this.request.setSecure(true);
this.repository.setSecure(Boolean.FALSE);
CsrfToken token = this.repository.generateToken(this.request);
this.repository.saveToken(token, this.request, this.response);
Cookie tokenCookie = this.response.getCookie(DSpaceCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME);
assertThat(tokenCookie.getSecure()).isFalse();
}
@Test
public 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(DSpaceCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME);
assertThat(tokenCookie.getSecure()).isFalse();
}
@Test
public void saveTokenNull() {
this.request.setSecure(true);
this.repository.saveToken(null, this.request, this.response);
Cookie tokenCookie = this.response
.getCookie(DSpaceCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME);
Cookie tokenCookie = this.response.getCookie(DSpaceCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME);
assertThat(tokenCookie.getMaxAge()).isZero();
assertThat(tokenCookie.getName())
.isEqualTo(DSpaceCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME);
assertThat(tokenCookie.getName()).isEqualTo(DSpaceCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME);
assertThat(tokenCookie.getPath()).isEqualTo(this.request.getContextPath());
assertThat(tokenCookie.getSecure()).isEqualTo(this.request.isSecure());
assertThat(tokenCookie.getValue()).isEmpty();
@@ -126,10 +171,16 @@ public class DSpaceCsrfTokenRepositoryTest {
this.repository.setCookieHttpOnly(true);
CsrfToken token = this.repository.generateToken(this.request);
this.repository.saveToken(token, this.request, this.response);
Cookie tokenCookie = this.response.getCookie(DSpaceCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME);
assertThat(tokenCookie.isHttpOnly()).isTrue();
}
Cookie tokenCookie = this.response
.getCookie(DSpaceCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME);
@Test
public 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(DSpaceCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME);
assertThat(tokenCookie.isHttpOnly()).isTrue();
}
@@ -138,10 +189,16 @@ public class DSpaceCsrfTokenRepositoryTest {
this.repository.setCookieHttpOnly(false);
CsrfToken token = this.repository.generateToken(this.request);
this.repository.saveToken(token, this.request, this.response);
Cookie tokenCookie = this.response.getCookie(DSpaceCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME);
assertThat(tokenCookie.isHttpOnly()).isFalse();
}
Cookie tokenCookie = this.response
.getCookie(DSpaceCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME);
@Test
public 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(DSpaceCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME);
assertThat(tokenCookie.isHttpOnly()).isFalse();
}
@@ -150,10 +207,7 @@ public class DSpaceCsrfTokenRepositoryTest {
this.repository = DSpaceCsrfTokenRepository.withHttpOnlyFalse();
CsrfToken token = this.repository.generateToken(this.request);
this.repository.saveToken(token, this.request, this.response);
Cookie tokenCookie = this.response
.getCookie(DSpaceCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME);
Cookie tokenCookie = this.response.getCookie(DSpaceCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME);
assertThat(tokenCookie.isHttpOnly()).isFalse();
}
@@ -163,10 +217,7 @@ public class DSpaceCsrfTokenRepositoryTest {
this.repository.setCookiePath(customPath);
CsrfToken token = this.repository.generateToken(this.request);
this.repository.saveToken(token, this.request, this.response);
Cookie tokenCookie = this.response
.getCookie(DSpaceCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME);
Cookie tokenCookie = this.response.getCookie(DSpaceCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME);
assertThat(tokenCookie.getPath()).isEqualTo(this.repository.getCookiePath());
}
@@ -176,10 +227,7 @@ public class DSpaceCsrfTokenRepositoryTest {
this.repository.setCookiePath(customPath);
CsrfToken token = this.repository.generateToken(this.request);
this.repository.saveToken(token, this.request, this.response);
Cookie tokenCookie = this.response
.getCookie(DSpaceCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME);
Cookie tokenCookie = this.response.getCookie(DSpaceCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME);
assertThat(tokenCookie.getPath()).isEqualTo(this.request.getContextPath());
}
@@ -189,10 +237,7 @@ public class DSpaceCsrfTokenRepositoryTest {
this.repository.setCookiePath(customPath);
CsrfToken token = this.repository.generateToken(this.request);
this.repository.saveToken(token, this.request, this.response);
Cookie tokenCookie = this.response
.getCookie(DSpaceCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME);
Cookie tokenCookie = this.response.getCookie(DSpaceCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME);
assertThat(tokenCookie.getPath()).isEqualTo(this.request.getContextPath());
}
@@ -200,16 +245,82 @@ public class DSpaceCsrfTokenRepositoryTest {
public void saveTokenWithCookieDomain() {
String domainName = "example.com";
this.repository.setCookieDomain(domainName);
CsrfToken token = this.repository.generateToken(this.request);
this.repository.saveToken(token, this.request, this.response);
Cookie tokenCookie = this.response
.getCookie(DSpaceCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME);
Cookie tokenCookie = this.response.getCookie(DSpaceCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME);
assertThat(tokenCookie.getDomain()).isEqualTo(domainName);
}
@Test
public 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(DSpaceCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME);
assertThat(tokenCookie.getDomain()).isEqualTo(domainName);
}
@Test
public void saveTokenWithCookieMaxAge() {
int maxAge = 1200;
this.repository.setCookieMaxAge(maxAge);
CsrfToken token = this.repository.generateToken(this.request);
this.repository.saveToken(token, this.request, this.response);
Cookie tokenCookie = this.response.getCookie(DSpaceCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME);
assertThat(tokenCookie.getMaxAge()).isEqualTo(maxAge);
}
@Test
public 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(DSpaceCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME);
assertThat(tokenCookie.getMaxAge()).isEqualTo(maxAge);
}
@Test
public 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(DSpaceCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME);
assertThat(tokenCookie.getAttribute("SameSite")).isNull();
}
@Test
public 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(DSpaceCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME);
assertThat(tokenCookie.getAttribute("SameSite")).isEqualTo(sameSitePolicy);
}
@Test
public 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(DSpaceCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME);
assertThat(tokenCookie.getAttribute("SameSite")).isEqualTo(sameSitePolicy);
}
@Test
public void saveTokenWithExistingSetCookieThenDoesNotOverwrite() {
this.response.setHeader(HttpHeaders.SET_COOKIE, "MyCookie=test");
this.repository = new DSpaceCsrfTokenRepository();
CsrfToken token = this.repository.generateToken(this.request);
this.repository.saveToken(token, this.request, this.response);
assertThat(this.response.getCookie("MyCookie")).isNotNull();
assertThat(this.response.getCookie(DSpaceCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME)).isNotNull();
}
@Test
public void loadTokenNoCookiesNull() {
assertThat(this.repository.loadToken(this.request)).isNull();
@@ -218,32 +329,24 @@ public class DSpaceCsrfTokenRepositoryTest {
@Test
public void loadTokenCookieIncorrectNameNull() {
this.request.setCookies(new Cookie("other", "name"));
assertThat(this.repository.loadToken(this.request)).isNull();
}
@Test
public void loadTokenCookieValueEmptyString() {
this.request.setCookies(
new Cookie(DSpaceCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME, ""));
this.request.setCookies(new Cookie(DSpaceCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME, ""));
assertThat(this.repository.loadToken(this.request)).isNull();
}
@Test
public void loadToken() {
CsrfToken generateToken = this.repository.generateToken(this.request);
this.request
.setCookies(new Cookie(DSpaceCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME,
generateToken.getToken()));
.setCookies(new Cookie(DSpaceCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME, generateToken.getToken()));
CsrfToken loadToken = this.repository.loadToken(this.request);
assertThat(loadToken).isNotNull();
assertThat(loadToken.getHeaderName()).isEqualTo(generateToken.getHeaderName());
assertThat(loadToken.getParameterName())
.isEqualTo(generateToken.getParameterName());
assertThat(loadToken.getParameterName()).isEqualTo(generateToken.getParameterName());
assertThat(loadToken.getToken()).isNotEmpty();
}
@@ -256,32 +359,95 @@ public class DSpaceCsrfTokenRepositoryTest {
this.repository.setHeaderName(headerName);
this.repository.setParameterName(parameterName);
this.repository.setCookieName(cookieName);
this.request.setCookies(new Cookie(cookieName, value));
CsrfToken loadToken = this.repository.loadToken(this.request);
assertThat(loadToken).isNotNull();
assertThat(loadToken.getHeaderName()).isEqualTo(headerName);
assertThat(loadToken.getParameterName()).isEqualTo(parameterName);
assertThat(loadToken.getToken()).isEqualTo(value);
}
@Test(expected = IllegalArgumentException.class)
@Test
public void loadDeferredTokenWhenDoesNotExistThenGeneratedAndSaved() {
DeferredCsrfToken deferredCsrfToken = this.repository.loadDeferredToken(this.request, this.response);
CsrfToken csrfToken = deferredCsrfToken.get();
assertThat(csrfToken).isNotNull();
assertThat(deferredCsrfToken.isGenerated()).isTrue();
Cookie tokenCookie = this.response.getCookie(DSpaceCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME);
assertThat(tokenCookie).isNotNull();
assertThat(tokenCookie.getMaxAge()).isEqualTo(-1);
assertThat(tokenCookie.getName()).isEqualTo(DSpaceCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME);
assertThat(tokenCookie.getPath()).isEqualTo(this.request.getContextPath());
assertThat(tokenCookie.getSecure()).isEqualTo(this.request.isSecure());
assertThat(tokenCookie.getValue()).isEqualTo(csrfToken.getToken());
assertThat(tokenCookie.isHttpOnly()).isEqualTo(true);
}
@Test
public void loadDeferredTokenWhenExistsAndNullSavedThenGeneratedAndSaved() {
CsrfToken generatedToken = this.repository.generateToken(this.request);
this.request
.setCookies(new Cookie(DSpaceCsrfTokenRepository.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 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(DSpaceCsrfTokenRepository.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(tokenCookie.getAttribute("SameSite")).isEqualTo(sameSitePolicy);
}
@Test
public void withHttpOnlyFalseWhenCookieCustomizerThenStillDefaultsToFalse() {
DSpaceCsrfTokenRepository repository = DSpaceCsrfTokenRepository.withHttpOnlyFalse();
repository.setCookieCustomizer((customizer) -> customizer.maxAge(1000));
CsrfToken token = repository.generateToken(this.request);
repository.saveToken(token, this.request, this.response);
Cookie tokenCookie = this.response.getCookie(DSpaceCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME);
assertThat(tokenCookie).isNotNull();
assertThat(tokenCookie.getMaxAge()).isEqualTo(1000);
assertThat(tokenCookie.isHttpOnly()).isEqualTo(Boolean.FALSE);
}
@Test
public void setCookieNameNullIllegalArgumentException() {
this.repository.setCookieName(null);
assertThatIllegalArgumentException().isThrownBy(() -> this.repository.setCookieName(null));
}
@Test(expected = IllegalArgumentException.class)
@Test
public void setParameterNameNullIllegalArgumentException() {
this.repository.setParameterName(null);
assertThatIllegalArgumentException().isThrownBy(() -> this.repository.setParameterName(null));
}
@Test(expected = IllegalArgumentException.class)
@Test
public void setHeaderNameNullIllegalArgumentException() {
this.repository.setHeaderName(null);
assertThatIllegalArgumentException().isThrownBy(() -> this.repository.setHeaderName(null));
}
@Test
public void setCookieMaxAgeZeroIllegalArgumentException() {
assertThatIllegalArgumentException().isThrownBy(() -> this.repository.setCookieMaxAge(0));
}
}