Add deferred CsrfTokenRepository.loadDeferredToken

* Move DeferredCsrfToken to top-level and implement Supplier<CsrfToken>
* Move RepositoryDeferredCsrfToken to top-level and make package-private
* Add CsrfTokenRepository.loadToken(HttpServletRequest, HttpServletResponse)
* Update CsrfFilter
* Rename CsrfTokenRepositoryRequestHandler to CsrfTokenRequestAttributeHandler

Issue gh-11892
Closes gh-11918
This commit is contained in:
Steve Riesenberg
2022-09-27 14:53:54 -05:00
parent 0e215a21ad
commit 475b3bb6bb
31 changed files with 536 additions and 353 deletions

View File

@@ -39,17 +39,17 @@ public final class CsrfAuthenticationStrategy implements SessionAuthenticationSt
private final Log logger = LogFactory.getLog(getClass());
private final CsrfTokenRepository csrfTokenRepository;
private final CsrfTokenRepository tokenRepository;
private CsrfTokenRequestHandler requestHandler;
private CsrfTokenRequestHandler requestHandler = new CsrfTokenRequestAttributeHandler();
/**
* Creates a new instance
* @param csrfTokenRepository the {@link CsrfTokenRepository} to use
* @param tokenRepository the {@link CsrfTokenRepository} to use
*/
public CsrfAuthenticationStrategy(CsrfTokenRepository csrfTokenRepository) {
this.requestHandler = new CsrfTokenRepositoryRequestHandler(csrfTokenRepository);
this.csrfTokenRepository = csrfTokenRepository;
public CsrfAuthenticationStrategy(CsrfTokenRepository tokenRepository) {
Assert.notNull(tokenRepository, "tokenRepository cannot be null");
this.tokenRepository = tokenRepository;
}
/**
@@ -65,8 +65,9 @@ public final class CsrfAuthenticationStrategy implements SessionAuthenticationSt
@Override
public void onAuthentication(Authentication authentication, HttpServletRequest request,
HttpServletResponse response) throws SessionAuthenticationException {
this.csrfTokenRepository.saveToken(null, request, response);
this.requestHandler.handle(request, response);
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

@@ -82,30 +82,21 @@ public final class CsrfFilter extends OncePerRequestFilter {
private final Log logger = LogFactory.getLog(getClass());
private final CsrfTokenRequestHandler requestHandler;
private final CsrfTokenRepository tokenRepository;
private RequestMatcher requireCsrfProtectionMatcher = DEFAULT_CSRF_MATCHER;
private AccessDeniedHandler accessDeniedHandler = new AccessDeniedHandlerImpl();
/**
* Creates a new instance.
* @param csrfTokenRepository the {@link CsrfTokenRepository} to use
* @deprecated Use {@link CsrfFilter#CsrfFilter(CsrfTokenRequestHandler)} instead
*/
@Deprecated
public CsrfFilter(CsrfTokenRepository csrfTokenRepository) {
this(new CsrfTokenRepositoryRequestHandler(csrfTokenRepository));
}
private CsrfTokenRequestHandler requestHandler = new CsrfTokenRequestAttributeHandler();
/**
* Creates a new instance.
* @param requestHandler the {@link CsrfTokenRequestHandler} to use. Default is
* {@link CsrfTokenRepositoryRequestHandler}.
* @param tokenRepository the {@link CsrfTokenRepository} to use
*/
public CsrfFilter(CsrfTokenRequestHandler requestHandler) {
Assert.notNull(requestHandler, "requestHandler cannot be null");
this.requestHandler = requestHandler;
public CsrfFilter(CsrfTokenRepository tokenRepository) {
Assert.notNull(tokenRepository, "tokenRepository cannot be null");
this.tokenRepository = tokenRepository;
}
@Override
@@ -116,7 +107,8 @@ public final class CsrfFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
DeferredCsrfToken deferredCsrfToken = this.requestHandler.handle(request, response);
DeferredCsrfToken deferredCsrfToken = this.tokenRepository.loadDeferredToken(request, response);
this.requestHandler.handle(request, response, deferredCsrfToken::get);
if (!this.requireCsrfProtectionMatcher.matches(request)) {
if (this.logger.isTraceEnabled()) {
this.logger.trace("Did not protect against CSRF since request did not match "
@@ -174,6 +166,21 @@ public final class CsrfFilter extends OncePerRequestFilter {
this.accessDeniedHandler = accessDeniedHandler;
}
/**
* Specifies a {@link CsrfTokenRequestHandler} that is used to make the
* {@link CsrfToken} available as a request attribute.
*
* <p>
* The default is {@link CsrfTokenRequestAttributeHandler}.
* </p>
* @param requestHandler the {@link CsrfTokenRequestHandler} to use
* @since 5.8
*/
public void setRequestHandler(CsrfTokenRequestHandler requestHandler) {
Assert.notNull(requestHandler, "requestHandler cannot be null");
this.requestHandler = requestHandler;
}
/**
* Constant time comparison to prevent against timing attacks.
* @param expected

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,6 +26,7 @@ import javax.servlet.http.HttpSession;
* {@link HttpSession}.
*
* @author Rob Winch
* @author Steve Riesenberg
* @since 3.2
* @see HttpSessionCsrfTokenRepository
*/
@@ -55,4 +56,20 @@ public interface CsrfTokenRepository {
*/
CsrfToken loadToken(HttpServletRequest request);
/**
* Defers loading the {@link CsrfToken} using the {@link HttpServletRequest} and
* {@link HttpServletResponse} until it is needed by the application.
* <p>
* The returned {@link DeferredCsrfToken} is cached to allow subsequent calls to
* {@link DeferredCsrfToken#get()} to return the same {@link CsrfToken} without the
* cost of loading or generating the token again.
* @param request the {@link HttpServletRequest} to use
* @param response the {@link HttpServletResponse} to use
* @return a {@link DeferredCsrfToken} that will load the {@link CsrfToken}
* @since 5.8
*/
default DeferredCsrfToken loadDeferredToken(HttpServletRequest request, HttpServletResponse response) {
return new RepositoryDeferredCsrfToken(this, request, response);
}
}

View File

@@ -31,29 +31,10 @@ import org.springframework.util.Assert;
* @author Steve Riesenberg
* @since 5.8
*/
public class CsrfTokenRepositoryRequestHandler implements CsrfTokenRequestHandler {
private final CsrfTokenRepository csrfTokenRepository;
public class CsrfTokenRequestAttributeHandler implements CsrfTokenRequestHandler {
private String csrfRequestAttributeName;
/**
* Creates a new instance.
*/
public CsrfTokenRepositoryRequestHandler() {
this(new HttpSessionCsrfTokenRepository());
}
/**
* Creates a new instance.
* @param csrfTokenRepository the {@link CsrfTokenRepository} to use. Default
* {@link HttpSessionCsrfTokenRepository}
*/
public CsrfTokenRepositoryRequestHandler(CsrfTokenRepository csrfTokenRepository) {
Assert.notNull(csrfTokenRepository, "csrfTokenRepository cannot be null");
this.csrfTokenRepository = csrfTokenRepository;
}
/**
* The {@link CsrfToken} is available as a request attribute named
* {@code CsrfToken.class.getName()}. By default, an additional request attribute that
@@ -67,18 +48,18 @@ public class CsrfTokenRepositoryRequestHandler implements CsrfTokenRequestHandle
}
@Override
public DeferredCsrfToken handle(HttpServletRequest request, HttpServletResponse response) {
public void handle(HttpServletRequest request, HttpServletResponse response,
Supplier<CsrfToken> deferredCsrfToken) {
Assert.notNull(request, "request cannot be null");
Assert.notNull(response, "response cannot be null");
Assert.notNull(deferredCsrfToken, "deferredCsrfToken cannot be null");
request.setAttribute(HttpServletResponse.class.getName(), response);
DeferredCsrfToken deferredCsrfToken = new RepositoryDeferredCsrfToken(request, response);
CsrfToken csrfToken = new SupplierCsrfToken(deferredCsrfToken::get);
CsrfToken csrfToken = new SupplierCsrfToken(deferredCsrfToken);
request.setAttribute(CsrfToken.class.getName(), csrfToken);
String csrfAttrName = (this.csrfRequestAttributeName != null) ? this.csrfRequestAttributeName
: csrfToken.getParameterName();
request.setAttribute(csrfAttrName, csrfToken);
return deferredCsrfToken;
}
private static final class SupplierCsrfToken implements CsrfToken {
@@ -114,46 +95,4 @@ public class CsrfTokenRepositoryRequestHandler implements CsrfTokenRequestHandle
}
private final class RepositoryDeferredCsrfToken implements DeferredCsrfToken {
private final HttpServletRequest request;
private final HttpServletResponse response;
private CsrfToken csrfToken;
private Boolean missingToken;
RepositoryDeferredCsrfToken(HttpServletRequest request, HttpServletResponse response) {
this.request = request;
this.response = response;
}
@Override
public CsrfToken get() {
init();
return this.csrfToken;
}
@Override
public boolean isGenerated() {
init();
return this.missingToken;
}
private void init() {
if (this.csrfToken != null) {
return;
}
this.csrfToken = CsrfTokenRepositoryRequestHandler.this.csrfTokenRepository.loadToken(this.request);
this.missingToken = (this.csrfToken == null);
if (this.missingToken) {
this.csrfToken = CsrfTokenRepositoryRequestHandler.this.csrfTokenRepository.generateToken(this.request);
CsrfTokenRepositoryRequestHandler.this.csrfTokenRepository.saveToken(this.csrfToken, this.request,
this.response);
}
}
}
}

View File

@@ -16,20 +16,22 @@
package org.springframework.security.web.csrf;
import java.util.function.Supplier;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.util.Assert;
/**
* An interface that is used to determine the {@link CsrfToken} to use and make the
* {@link CsrfToken} available as a request attribute. Implementations of this interface
* may choose to perform additional tasks or customize how the token is made available to
* the application through request attributes.
* A callback interface that is used to make the {@link CsrfToken} created by the
* {@link CsrfTokenRepository} available as a request attribute. Implementations of this
* interface may choose to perform additional tasks or customize how the token is made
* available to the application through request attributes.
*
* @author Steve Riesenberg
* @since 5.8
* @see CsrfTokenRepositoryRequestHandler
* @see CsrfTokenRequestAttributeHandler
*/
@FunctionalInterface
public interface CsrfTokenRequestHandler extends CsrfTokenRequestResolver {
@@ -38,8 +40,9 @@ public interface CsrfTokenRequestHandler extends CsrfTokenRequestResolver {
* Handles a request using a {@link CsrfToken}.
* @param request the {@code HttpServletRequest} being handled
* @param response the {@code HttpServletResponse} being handled
* @param csrfToken the {@link CsrfToken} created by the {@link CsrfTokenRepository}
*/
DeferredCsrfToken handle(HttpServletRequest request, HttpServletResponse response);
void handle(HttpServletRequest request, HttpServletResponse response, Supplier<CsrfToken> csrfToken);
@Override
default String resolveCsrfTokenValue(HttpServletRequest request, CsrfToken csrfToken) {

View File

@@ -25,7 +25,7 @@ import javax.servlet.http.HttpServletRequest;
*
* @author Steve Riesenberg
* @since 5.8
* @see CsrfTokenRepositoryRequestHandler
* @see CsrfTokenRequestAttributeHandler
*/
@FunctionalInterface
public interface CsrfTokenRequestResolver {

View File

@@ -20,11 +20,12 @@ package org.springframework.security.web.csrf;
* An interface that allows delayed access to a {@link CsrfToken} that may be generated.
*
* @author Rob Winch
* @author Steve Riesenberg
* @since 5.8
*/
public interface DeferredCsrfToken {
/***
/**
* Gets the {@link CsrfToken}
* @return a non-null {@link CsrfToken}
*/

View File

@@ -27,8 +27,9 @@ import org.springframework.util.Assert;
*
* @author Rob Winch
* @since 4.1
* @deprecated Use org.springframework.security.web.csrf.CsrfTokenRequestHandler which
* returns a {@link DeferredCsrfToken}
* @deprecated Use
* {@link CsrfTokenRepository#loadDeferredToken(HttpServletRequest, HttpServletResponse)}
* which returns a {@link DeferredCsrfToken}
*/
@Deprecated
public final class LazyCsrfTokenRepository implements CsrfTokenRepository {

View File

@@ -0,0 +1,71 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.csrf;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author Rob Winch
* @author Steve Riesenberg
* @since 5.8
*/
final class RepositoryDeferredCsrfToken implements DeferredCsrfToken {
private final CsrfTokenRepository csrfTokenRepository;
private final HttpServletRequest request;
private final HttpServletResponse response;
private CsrfToken csrfToken;
private boolean missingToken;
RepositoryDeferredCsrfToken(CsrfTokenRepository csrfTokenRepository, HttpServletRequest request,
HttpServletResponse response) {
this.csrfTokenRepository = csrfTokenRepository;
this.request = request;
this.response = response;
}
@Override
public CsrfToken get() {
init();
return this.csrfToken;
}
@Override
public boolean isGenerated() {
init();
return this.missingToken;
}
private void init() {
if (this.csrfToken != null) {
return;
}
this.csrfToken = this.csrfTokenRepository.loadToken(this.request);
this.missingToken = (this.csrfToken == null);
if (this.missingToken) {
this.csrfToken = this.csrfTokenRepository.generateToken(this.request);
this.csrfTokenRepository.saveToken(this.csrfToken, this.request, this.response);
}
}
}