Browse Source

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>
yybmion 4 months ago
parent
commit
d48c463c03

+ 3 - 0
web/src/main/java/org/springframework/security/web/csrf/CsrfFilter.java

@@ -119,6 +119,9 @@ public final class CsrfFilter extends OncePerRequestFilter {
 		}
 		}
 		CsrfToken csrfToken = deferredCsrfToken.get();
 		CsrfToken csrfToken = deferredCsrfToken.get();
 		String actualToken = this.requestHandler.resolveCsrfTokenValue(request, csrfToken);
 		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)) {
 		if (!equalsConstantTime(csrfToken.getToken(), actualToken)) {
 			boolean missingToken = deferredCsrfToken.isGenerated();
 			boolean missingToken = deferredCsrfToken.isGenerated();
 			this.logger
 			this.logger

+ 10 - 1
web/src/main/java/org/springframework/security/web/csrf/CsrfTokenRequestAttributeHandler.java

@@ -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");
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with 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.HttpServletRequest;
 import jakarta.servlet.http.HttpServletResponse;
 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;
 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.
  * value as either a header or parameter value of the request.
  *
  *
  * @author Steve Riesenberg
  * @author Steve Riesenberg
+ * @author Yoobin Yoon
  * @since 5.8
  * @since 5.8
  */
  */
 public class CsrfTokenRequestAttributeHandler implements CsrfTokenRequestHandler {
 public class CsrfTokenRequestAttributeHandler implements CsrfTokenRequestHandler {
 
 
+	private static final Log logger = LogFactory.getLog(CsrfTokenRequestAttributeHandler.class);
+
 	private String csrfRequestAttributeName = "_csrf";
 	private String csrfRequestAttributeName = "_csrf";
 
 
 	/**
 	/**
@@ -60,6 +66,9 @@ public class CsrfTokenRequestAttributeHandler implements CsrfTokenRequestHandler
 		String csrfAttrName = (this.csrfRequestAttributeName != null) ? this.csrfRequestAttributeName
 		String csrfAttrName = (this.csrfRequestAttributeName != null) ? this.csrfRequestAttributeName
 				: csrfToken.getParameterName();
 				: csrfToken.getParameterName();
 		request.setAttribute(csrfAttrName, csrfToken);
 		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")
 	@SuppressWarnings("serial")

+ 16 - 4
web/src/main/java/org/springframework/security/web/csrf/CsrfTokenRequestHandler.java

@@ -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");
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with 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.HttpServletRequest;
 import jakarta.servlet.http.HttpServletResponse;
 import jakarta.servlet.http.HttpServletResponse;
 
 
+import org.springframework.core.log.LogMessage;
 import org.springframework.util.Assert;
 import org.springframework.util.Assert;
 
 
 /**
 /**
@@ -30,6 +31,7 @@ import org.springframework.util.Assert;
  * available to the application through request attributes.
  * available to the application through request attributes.
  *
  *
  * @author Steve Riesenberg
  * @author Steve Riesenberg
+ * @author Yoobin Yoon
  * @since 5.8
  * @since 5.8
  * @see CsrfTokenRequestAttributeHandler
  * @see CsrfTokenRequestAttributeHandler
  */
  */
@@ -49,10 +51,20 @@ public interface CsrfTokenRequestHandler extends CsrfTokenRequestResolver {
 		Assert.notNull(request, "request cannot be null");
 		Assert.notNull(request, "request cannot be null");
 		Assert.notNull(csrfToken, "csrfToken cannot be null");
 		Assert.notNull(csrfToken, "csrfToken cannot be null");
 		String actualToken = request.getHeader(csrfToken.getHeaderName());
 		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;
 	}
 	}
 
 
 }
 }

+ 32 - 0
web/src/main/java/org/springframework/security/web/csrf/CsrfTokenRequestHandlerLoggerHolder.java

@@ -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() {
+	}
+
+}

+ 14 - 1
web/src/main/java/org/springframework/security/web/csrf/XorCsrfTokenRequestAttributeHandler.java

@@ -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");
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with 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.HttpServletRequest;
 import jakarta.servlet.http.HttpServletResponse;
 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.security.crypto.codec.Utf8;
 import org.springframework.util.Assert;
 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.
  * value from the masked value as either a header or parameter value of the request.
  *
  *
  * @author Steve Riesenberg
  * @author Steve Riesenberg
+ * @author Yoobin Yoon
  * @since 5.8
  * @since 5.8
  */
  */
 public final class XorCsrfTokenRequestAttributeHandler extends CsrfTokenRequestAttributeHandler {
 public final class XorCsrfTokenRequestAttributeHandler extends CsrfTokenRequestAttributeHandler {
 
 
+	private static final Log logger = LogFactory.getLog(XorCsrfTokenRequestAttributeHandler.class);
+
 	private SecureRandom secureRandom = new SecureRandom();
 	private SecureRandom secureRandom = new SecureRandom();
 
 
 	/**
 	/**
@@ -70,6 +76,9 @@ public final class XorCsrfTokenRequestAttributeHandler extends CsrfTokenRequestA
 	@Override
 	@Override
 	public String resolveCsrfTokenValue(HttpServletRequest request, CsrfToken csrfToken) {
 	public String resolveCsrfTokenValue(HttpServletRequest request, CsrfToken csrfToken) {
 		String actualToken = super.resolveCsrfTokenValue(request, csrfToken);
 		String actualToken = super.resolveCsrfTokenValue(request, csrfToken);
+		if (actualToken == null) {
+			return null;
+		}
 		return getTokenValue(actualToken, csrfToken.getToken());
 		return getTokenValue(actualToken, csrfToken.getToken());
 	}
 	}
 
 
@@ -79,12 +88,16 @@ public final class XorCsrfTokenRequestAttributeHandler extends CsrfTokenRequestA
 			actualBytes = Base64.getUrlDecoder().decode(actualToken);
 			actualBytes = Base64.getUrlDecoder().decode(actualToken);
 		}
 		}
 		catch (Exception ex) {
 		catch (Exception ex) {
+			logger.trace(LogMessage.format("Not returning the CSRF token since it's not Base64-encoded"), ex);
 			return null;
 			return null;
 		}
 		}
 
 
 		byte[] tokenBytes = Utf8.encode(token);
 		byte[] tokenBytes = Utf8.encode(token);
 		int tokenSize = tokenBytes.length;
 		int tokenSize = tokenBytes.length;
 		if (actualBytes.length != tokenSize * 2) {
 		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;
 			return null;
 		}
 		}
 
 

+ 8 - 1
web/src/main/java/org/springframework/security/web/server/csrf/ServerCsrfTokenRequestAttributeHandler.java

@@ -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");
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with 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;
 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 reactor.core.publisher.Mono;
 
 
+import org.springframework.core.log.LogMessage;
 import org.springframework.http.HttpHeaders;
 import org.springframework.http.HttpHeaders;
 import org.springframework.http.MediaType;
 import org.springframework.http.MediaType;
 import org.springframework.http.codec.multipart.FormFieldPart;
 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.
  * resolving the token value as either a form data value or header of the request.
  *
  *
  * @author Steve Riesenberg
  * @author Steve Riesenberg
+ * @author Yoobin Yoon
  * @since 5.8
  * @since 5.8
  */
  */
 public class ServerCsrfTokenRequestAttributeHandler implements ServerCsrfTokenRequestHandler {
 public class ServerCsrfTokenRequestAttributeHandler implements ServerCsrfTokenRequestHandler {
 
 
+	private static final Log logger = LogFactory.getLog(ServerCsrfTokenRequestAttributeHandler.class);
+
 	private boolean isTokenFromMultipartDataEnabled;
 	private boolean isTokenFromMultipartDataEnabled;
 
 
 	@Override
 	@Override
@@ -42,6 +48,7 @@ public class ServerCsrfTokenRequestAttributeHandler implements ServerCsrfTokenRe
 		Assert.notNull(exchange, "exchange cannot be null");
 		Assert.notNull(exchange, "exchange cannot be null");
 		Assert.notNull(csrfToken, "csrfToken cannot be null");
 		Assert.notNull(csrfToken, "csrfToken cannot be null");
 		exchange.getAttributes().put(CsrfToken.class.getName(), csrfToken);
 		exchange.getAttributes().put(CsrfToken.class.getName(), csrfToken);
+		logger.trace(LogMessage.format("Wrote a CSRF token to the [%s] exchange attribute", CsrfToken.class.getName()));
 	}
 	}
 
 
 	@Override
 	@Override

+ 22 - 3
web/src/main/java/org/springframework/security/web/server/csrf/ServerCsrfTokenRequestHandler.java

@@ -18,6 +18,7 @@ package org.springframework.security.web.server.csrf;
 
 
 import reactor.core.publisher.Mono;
 import reactor.core.publisher.Mono;
 
 
+import org.springframework.core.log.LogMessage;
 import org.springframework.util.Assert;
 import org.springframework.util.Assert;
 import org.springframework.web.server.ServerWebExchange;
 import org.springframework.web.server.ServerWebExchange;
 
 
@@ -46,9 +47,27 @@ public interface ServerCsrfTokenRequestHandler extends ServerCsrfTokenRequestRes
 	default Mono<String> resolveCsrfTokenValue(ServerWebExchange exchange, CsrfToken csrfToken) {
 	default Mono<String> resolveCsrfTokenValue(ServerWebExchange exchange, CsrfToken csrfToken) {
 		Assert.notNull(exchange, "exchange cannot be null");
 		Assert.notNull(exchange, "exchange cannot be null");
 		Assert.notNull(csrfToken, "csrfToken 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();
+		}));
 	}
 	}
 
 
 }
 }

+ 32 - 0
web/src/main/java/org/springframework/security/web/server/csrf/ServerCsrfTokenRequestHandlerLoggerHolder.java

@@ -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() {
+	}
+
+}

+ 11 - 1
web/src/main/java/org/springframework/security/web/server/csrf/XorServerCsrfTokenRequestAttributeHandler.java

@@ -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");
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with 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.security.SecureRandom;
 import java.util.Base64;
 import java.util.Base64;
 
 
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
 import reactor.core.publisher.Mono;
 import reactor.core.publisher.Mono;
 
 
+import org.springframework.core.log.LogMessage;
 import org.springframework.security.crypto.codec.Utf8;
 import org.springframework.security.crypto.codec.Utf8;
 import org.springframework.util.Assert;
 import org.springframework.util.Assert;
 import org.springframework.web.server.ServerWebExchange;
 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.
  * masked value as either a form data value or header of the request.
  *
  *
  * @author Steve Riesenberg
  * @author Steve Riesenberg
+ * @author Yoobin Yoon
  * @since 5.8
  * @since 5.8
  */
  */
 public final class XorServerCsrfTokenRequestAttributeHandler extends ServerCsrfTokenRequestAttributeHandler {
 public final class XorServerCsrfTokenRequestAttributeHandler extends ServerCsrfTokenRequestAttributeHandler {
 
 
+	private static final Log logger = LogFactory.getLog(XorServerCsrfTokenRequestAttributeHandler.class);
+
 	private SecureRandom secureRandom = new SecureRandom();
 	private SecureRandom secureRandom = new SecureRandom();
 
 
 	/**
 	/**
@@ -72,12 +78,16 @@ public final class XorServerCsrfTokenRequestAttributeHandler extends ServerCsrfT
 			actualBytes = Base64.getUrlDecoder().decode(actualToken);
 			actualBytes = Base64.getUrlDecoder().decode(actualToken);
 		}
 		}
 		catch (Exception ex) {
 		catch (Exception ex) {
+			logger.trace(LogMessage.format("Not returning the CSRF token since it's not Base64-encoded"), ex);
 			return null;
 			return null;
 		}
 		}
 
 
 		byte[] tokenBytes = Utf8.encode(token);
 		byte[] tokenBytes = Utf8.encode(token);
 		int tokenSize = tokenBytes.length;
 		int tokenSize = tokenBytes.length;
 		if (actualBytes.length != tokenSize * 2) {
 		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;
 			return null;
 		}
 		}