Add logging to CsrfTokenRequestHandler implementations

Add trace-level logging to show the logical path of CSRF token processing
- Log token source (header or parameter) in resolveCsrfTokenValue
- Log request attribute names in handle methods
- Log failures in XorCsrfTokenRequestAttributeHandler (especially Base64 decoding)
- Add similar logging to XorServerCsrfTokenRequestAttributeHandler

Improves debugging capabilities without changing functionality.

Closes gh-13626

Signed-off-by: yybmion <yunyubin54@gmail.com>
This commit is contained in:
yybmion 2025-04-25 02:26:48 +09:00 committed by Josh Cummings
parent e3c39f02bc
commit d48c463c03
No known key found for this signature in database
GPG Key ID: 869B37A20E876129
9 changed files with 148 additions and 11 deletions

View File

@ -119,6 +119,9 @@ public final class CsrfFilter extends OncePerRequestFilter {
}
CsrfToken csrfToken = deferredCsrfToken.get();
String actualToken = this.requestHandler.resolveCsrfTokenValue(request, csrfToken);
if (actualToken != null && this.logger.isTraceEnabled()) {
this.logger.trace(LogMessage.format("Found a CSRF token in the request"));
}
if (!equalsConstantTime(csrfToken.getToken(), actualToken)) {
boolean missingToken = deferredCsrfToken.isGenerated();
this.logger

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2025 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.
@ -20,7 +20,10 @@ import java.util.function.Supplier;
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.core.log.LogMessage;
import org.springframework.util.Assert;
/**
@ -29,10 +32,13 @@ import org.springframework.util.Assert;
* value as either a header or parameter value of the request.
*
* @author Steve Riesenberg
* @author Yoobin Yoon
* @since 5.8
*/
public class CsrfTokenRequestAttributeHandler implements CsrfTokenRequestHandler {
private static final Log logger = LogFactory.getLog(CsrfTokenRequestAttributeHandler.class);
private String csrfRequestAttributeName = "_csrf";
/**
@ -60,6 +66,9 @@ public class CsrfTokenRequestAttributeHandler implements CsrfTokenRequestHandler
String csrfAttrName = (this.csrfRequestAttributeName != null) ? this.csrfRequestAttributeName
: csrfToken.getParameterName();
request.setAttribute(csrfAttrName, csrfToken);
logger.trace(LogMessage.format("Wrote a CSRF token to the following request attributes: [%s, %s]", csrfAttrName,
CsrfToken.class.getName()));
}
@SuppressWarnings("serial")

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2025 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.
@ -21,6 +21,7 @@ import java.util.function.Supplier;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.core.log.LogMessage;
import org.springframework.util.Assert;
/**
@ -30,6 +31,7 @@ import org.springframework.util.Assert;
* available to the application through request attributes.
*
* @author Steve Riesenberg
* @author Yoobin Yoon
* @since 5.8
* @see CsrfTokenRequestAttributeHandler
*/
@ -49,10 +51,20 @@ public interface CsrfTokenRequestHandler extends CsrfTokenRequestResolver {
Assert.notNull(request, "request cannot be null");
Assert.notNull(csrfToken, "csrfToken cannot be null");
String actualToken = request.getHeader(csrfToken.getHeaderName());
if (actualToken == null) {
actualToken = request.getParameter(csrfToken.getParameterName());
if (actualToken != null) {
return actualToken;
}
return actualToken;
CsrfTokenRequestHandlerLoggerHolder.logger.trace(
LogMessage.format("Did not find a CSRF token in the [%s] request header", csrfToken.getHeaderName()));
actualToken = request.getParameter(csrfToken.getParameterName());
if (actualToken != null) {
return actualToken;
}
CsrfTokenRequestHandlerLoggerHolder.logger.trace(LogMessage
.format("Did not find a CSRF token in the [%s] request parameter", csrfToken.getParameterName()));
return null;
}
}

View File

@ -0,0 +1,32 @@
/*
* Copyright 2002-2025 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 org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Utility class for holding the logger for {@link CsrfTokenRequestHandler}
*/
final class CsrfTokenRequestHandlerLoggerHolder {
static final Log logger = LogFactory.getLog(CsrfTokenRequestHandler.class);
private CsrfTokenRequestHandlerLoggerHolder() {
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 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.
@ -22,7 +22,10 @@ import java.util.function.Supplier;
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.core.log.LogMessage;
import org.springframework.security.crypto.codec.Utf8;
import org.springframework.util.Assert;
@ -32,10 +35,13 @@ import org.springframework.util.Assert;
* value from the masked value as either a header or parameter value of the request.
*
* @author Steve Riesenberg
* @author Yoobin Yoon
* @since 5.8
*/
public final class XorCsrfTokenRequestAttributeHandler extends CsrfTokenRequestAttributeHandler {
private static final Log logger = LogFactory.getLog(XorCsrfTokenRequestAttributeHandler.class);
private SecureRandom secureRandom = new SecureRandom();
/**
@ -70,6 +76,9 @@ public final class XorCsrfTokenRequestAttributeHandler extends CsrfTokenRequestA
@Override
public String resolveCsrfTokenValue(HttpServletRequest request, CsrfToken csrfToken) {
String actualToken = super.resolveCsrfTokenValue(request, csrfToken);
if (actualToken == null) {
return null;
}
return getTokenValue(actualToken, csrfToken.getToken());
}
@ -79,12 +88,16 @@ public final class XorCsrfTokenRequestAttributeHandler extends CsrfTokenRequestA
actualBytes = Base64.getUrlDecoder().decode(actualToken);
}
catch (Exception ex) {
logger.trace(LogMessage.format("Not returning the CSRF token since it's not Base64-encoded"), ex);
return null;
}
byte[] tokenBytes = Utf8.encode(token);
int tokenSize = tokenBytes.length;
if (actualBytes.length != tokenSize * 2) {
logger.trace(LogMessage.format(
"Not returning the CSRF token since its Base64-decoded length (%d) is not equal to (%d)",
actualBytes.length, tokenSize * 2));
return null;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2025 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.
@ -16,8 +16,11 @@
package org.springframework.security.web.server.csrf;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import reactor.core.publisher.Mono;
import org.springframework.core.log.LogMessage;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.codec.multipart.FormFieldPart;
@ -31,10 +34,13 @@ import org.springframework.web.server.ServerWebExchange;
* resolving the token value as either a form data value or header of the request.
*
* @author Steve Riesenberg
* @author Yoobin Yoon
* @since 5.8
*/
public class ServerCsrfTokenRequestAttributeHandler implements ServerCsrfTokenRequestHandler {
private static final Log logger = LogFactory.getLog(ServerCsrfTokenRequestAttributeHandler.class);
private boolean isTokenFromMultipartDataEnabled;
@Override
@ -42,6 +48,7 @@ public class ServerCsrfTokenRequestAttributeHandler implements ServerCsrfTokenRe
Assert.notNull(exchange, "exchange cannot be null");
Assert.notNull(csrfToken, "csrfToken cannot be null");
exchange.getAttributes().put(CsrfToken.class.getName(), csrfToken);
logger.trace(LogMessage.format("Wrote a CSRF token to the [%s] exchange attribute", CsrfToken.class.getName()));
}
@Override

View File

@ -18,6 +18,7 @@ package org.springframework.security.web.server.csrf;
import reactor.core.publisher.Mono;
import org.springframework.core.log.LogMessage;
import org.springframework.util.Assert;
import org.springframework.web.server.ServerWebExchange;
@ -46,9 +47,27 @@ public interface ServerCsrfTokenRequestHandler extends ServerCsrfTokenRequestRes
default Mono<String> resolveCsrfTokenValue(ServerWebExchange exchange, CsrfToken csrfToken) {
Assert.notNull(exchange, "exchange cannot be null");
Assert.notNull(csrfToken, "csrfToken cannot be null");
return exchange.getFormData()
.flatMap((data) -> Mono.justOrEmpty(data.getFirst(csrfToken.getParameterName())))
.switchIfEmpty(Mono.justOrEmpty(exchange.getRequest().getHeaders().getFirst(csrfToken.getHeaderName())));
String headerName = csrfToken.getHeaderName();
String parameterName = csrfToken.getParameterName();
return exchange.getFormData().flatMap((data) -> {
String token = data.getFirst(parameterName);
if (token != null) {
return Mono.just(token);
}
ServerCsrfTokenRequestHandlerLoggerHolder.logger
.trace(LogMessage.format("Did not find a CSRF token in the [%s] request parameter", parameterName));
return Mono.empty();
}).switchIfEmpty(Mono.defer(() -> {
String token = exchange.getRequest().getHeaders().getFirst(headerName);
if (token != null) {
return Mono.just(token);
}
ServerCsrfTokenRequestHandlerLoggerHolder.logger
.trace(LogMessage.format("Did not find a CSRF token in the [%s] request header", headerName));
return Mono.empty();
}));
}
}

View File

@ -0,0 +1,32 @@
/*
* Copyright 2002-2025 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.server.csrf;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Utility class for holding the logger for {@link ServerCsrfTokenRequestHandler}
*/
final class ServerCsrfTokenRequestHandlerLoggerHolder {
static final Log logger = LogFactory.getLog(ServerCsrfTokenRequestHandler.class);
private ServerCsrfTokenRequestHandlerLoggerHolder() {
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 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.
@ -19,8 +19,11 @@ package org.springframework.security.web.server.csrf;
import java.security.SecureRandom;
import java.util.Base64;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import reactor.core.publisher.Mono;
import org.springframework.core.log.LogMessage;
import org.springframework.security.crypto.codec.Utf8;
import org.springframework.util.Assert;
import org.springframework.web.server.ServerWebExchange;
@ -32,10 +35,13 @@ import org.springframework.web.server.ServerWebExchange;
* masked value as either a form data value or header of the request.
*
* @author Steve Riesenberg
* @author Yoobin Yoon
* @since 5.8
*/
public final class XorServerCsrfTokenRequestAttributeHandler extends ServerCsrfTokenRequestAttributeHandler {
private static final Log logger = LogFactory.getLog(XorServerCsrfTokenRequestAttributeHandler.class);
private SecureRandom secureRandom = new SecureRandom();
/**
@ -72,12 +78,16 @@ public final class XorServerCsrfTokenRequestAttributeHandler extends ServerCsrfT
actualBytes = Base64.getUrlDecoder().decode(actualToken);
}
catch (Exception ex) {
logger.trace(LogMessage.format("Not returning the CSRF token since it's not Base64-encoded"), ex);
return null;
}
byte[] tokenBytes = Utf8.encode(token);
int tokenSize = tokenBytes.length;
if (actualBytes.length != tokenSize * 2) {
logger.trace(LogMessage.format(
"Not returning the CSRF token since its Base64-decoded length (%d) is not equal to (%d)",
actualBytes.length, tokenSize * 2));
return null;
}