Spring Boot中如何修改并记录请求载荷?
Got it, since you're already using CommonsRequestLoggingFilter in your Spring Boot app and want to mask sensitive data before logging, here's a practical, step-by-step solution:
The core issue here is that the native CommonsRequestLoggingFilter can't modify the request body directly. We'll solve this by wrapping the original request with a custom HttpServletRequestWrapper to sanitize sensitive fields, then pass this wrapped request to the logging filter.
Step 1: Create a Request Wrapper to Mask Sensitive Data
This wrapper reads the original request body, masks sensitive values (like passwords, credit card numbers), and makes the sanitized body available for both the logging filter and your application controllers.
import jakarta.servlet.ReadListener; import jakarta.servlet.ServletInputStream; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequestWrapper; import org.springframework.util.StreamUtils; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.util.regex.Matcher; import java.util.regex.Pattern; public class MaskedRequestWrapper extends HttpServletRequestWrapper { // Adjust this regex to match your sensitive fields (supports JSON format here) private static final Pattern SENSITIVE_FIELDS = Pattern.compile( "\"(password|creditCard|ssn|phone)\":\"([^\"]+)\"", Pattern.CASE_INSENSITIVE ); private final byte[] maskedBody; public MaskedRequestWrapper(HttpServletRequest request) throws IOException { super(request); // Read and store the original request body String originalBody = StreamUtils.copyToString(request.getInputStream(), StandardCharsets.UTF_8); // Mask sensitive data String sanitizedBody = maskSensitiveValues(originalBody); this.maskedBody = sanitizedBody.getBytes(StandardCharsets.UTF_8); } private String maskSensitiveValues(String originalBody) { Matcher matcher = SENSITIVE_FIELDS.matcher(originalBody); StringBuilder sanitized = new StringBuilder(); while (matcher.find()) { // Keep the field name, replace the value with *** matcher.appendReplacement(sanitized, "\"" + matcher.group(1) + "\":\"***\""); } matcher.appendTail(sanitized); return sanitized.toString(); } @Override public ServletInputStream getInputStream() throws IOException { return new ServletInputStream() { private final ByteArrayInputStream inputStream = new ByteArrayInputStream(maskedBody); @Override public boolean isFinished() { return inputStream.available() == 0; } @Override public boolean isReady() { return true; } @Override public void setReadListener(ReadListener readListener) {} @Override public int read() throws IOException { return inputStream.read(); } }; } @Override public InputStreamReader getReader() throws IOException { return new InputStreamReader(getInputStream(), StandardCharsets.UTF_8); } }
Step 2: Integrate the Wrapper with Your Logging Filter
You have two options here, pick the one that fits your existing setup:
Option A: Wrap the Request Before the Logging Filter
If you already have a registered CommonsRequestLoggingFilter, add a pre-filter to wrap the request first:
import jakarta.servlet.Filter; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import java.io.IOException; @Component @Order(Ordered.HIGHEST_PRECEDENCE) // Ensure this runs before the logging filter public class RequestMaskingFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { if (request instanceof HttpServletRequest) { HttpServletRequest maskedRequest = new MaskedRequestWrapper((HttpServletRequest) request); chain.doFilter(maskedRequest, response); } else { chain.doFilter(request, response); } } }
Option B: Extend CommonsRequestLoggingFilter Directly
If you're defining the logging filter via a @Bean, extend it to handle the wrapping internally:
import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.web.filter.CommonsRequestLoggingFilter; import java.io.IOException; public class MaskedCommonsRequestLoggingFilter extends CommonsRequestLoggingFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // Wrap the request first, then pass to parent logging logic HttpServletRequest maskedRequest = new MaskedRequestWrapper(request); super.doFilterInternal(maskedRequest, response, filterChain); } }
Then register this custom filter in your config:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class LoggingConfig { @Bean public MaskedCommonsRequestLoggingFilter maskedRequestLoggingFilter() { MaskedCommonsRequestLoggingFilter filter = new MaskedCommonsRequestLoggingFilter(); // Keep your existing logging config filter.setIncludeQueryString(true); filter.setIncludePayload(true); filter.setMaxPayloadLength(10000); filter.setAfterMessagePrefix("Sanitized Request Data: "); return filter; } }
Key Notes
- Request Body Reusability: The wrapper caches the request body as a byte array, so both the logging filter and your controllers can read it (since the original request input stream can only be read once).
- Adjust Regex for Your Data Format: The example uses JSON patterns. If you're handling form data (e.g.,
password=123456), update the regex to something likepassword=([^&]+)and replace withpassword=***. - Spring Boot Version: If you're on Spring Boot 2.x, replace
jakarta.servletimports withjavax.servlet.
Hope this works for your use case! You can tweak the sensitive field regex to match whatever data you need to hide.
内容的提问来源于stack exchange,提问作者joel

