Switch defaults and model for logging sensitive data
Issue: SPR-17029
This commit is contained in:
@@ -111,19 +111,13 @@ public interface CodecConfigurer {
|
||||
void jackson2JsonEncoder(Encoder<?> encoder);
|
||||
|
||||
/**
|
||||
* Whether to disable logging of request details for form and multipart
|
||||
* requests at any log level. By default such data is logged under
|
||||
* {@code "org.springframework.http.codec"} but may contain sensitive
|
||||
* information. Typically that's not an issue since DEBUG is used in
|
||||
* development, but this option may be used to explicitly disable any
|
||||
* logging of form and multipart data at any log level.
|
||||
* <p>By default this is set to {@code false} in which case form and
|
||||
* multipart data is logged at DEBUG or TRACE. When set to {@code true}
|
||||
* values will not be logged at any level.
|
||||
* @param disableLoggingRequestDetails whether to disable loggins
|
||||
* Whether to log form data at DEBUG level, and headers at TRACE level.
|
||||
* Both may contain sensitive information.
|
||||
* <p>By default set to {@code false} so that request details are not shown.
|
||||
* @param enable whether to enable or not
|
||||
* @since 5.1
|
||||
*/
|
||||
void disableLoggingRequestDetails(boolean disableLoggingRequestDetails);
|
||||
void enableLoggingRequestDetails(boolean enable);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -108,8 +108,10 @@ public class FormHttpMessageReader extends LoggingCodecSupport
|
||||
String body = charBuffer.toString();
|
||||
DataBufferUtils.release(buffer);
|
||||
MultiValueMap<String, String> formData = parseFormData(charset, body);
|
||||
if (shouldLogRequestDetails()) {
|
||||
logger.debug(Hints.getLogPrefix(hints) + "Decoded " + formData);
|
||||
if (logger.isDebugEnabled()) {
|
||||
String details = isEnableLoggingRequestDetails() ?
|
||||
formData.toString() : "form fields " + formData.keySet() + " (content masked)";
|
||||
logger.debug(Hints.getLogPrefix(hints) + "Read " + details);
|
||||
}
|
||||
return formData;
|
||||
});
|
||||
|
||||
@@ -131,8 +131,10 @@ public class FormHttpMessageWriter extends LoggingCodecSupport
|
||||
Assert.notNull(charset, "No charset"); // should never occur
|
||||
|
||||
return Mono.from(inputStream).flatMap(form -> {
|
||||
if (shouldLogRequestDetails()) {
|
||||
logger.debug(Hints.getLogPrefix(hints) + "Encoding " + form);
|
||||
if (logger.isDebugEnabled()) {
|
||||
String details = isEnableLoggingRequestDetails() ?
|
||||
form.toString() : "form fields " + form.keySet() + " (content masked)";
|
||||
logger.debug(Hints.getLogPrefix(hints) + "Writing " + details);
|
||||
}
|
||||
String value = serializeForm(form, charset);
|
||||
ByteBuffer byteBuffer = charset.encode(value);
|
||||
|
||||
@@ -33,41 +33,26 @@ public class LoggingCodecSupport {
|
||||
|
||||
protected final Log logger = HttpLog.create(LogFactory.getLog(getClass()));
|
||||
|
||||
/** Do not log potentially sensitive information (params at DEBUG and headers at TRACE). */
|
||||
private boolean disableLoggingRequestDetails = false;
|
||||
/** Whether to log potentially sensitive info (form data at DEBUG and headers at TRACE). */
|
||||
private boolean enableLoggingRequestDetails = false;
|
||||
|
||||
|
||||
/**
|
||||
* Whether to disable any logging of request details by this codec.
|
||||
* By default values being encoded or decoded are logged at DEBUG and TRACE
|
||||
* level under {@code "org.springframework.http.codec"} which may show
|
||||
* sensitive data for form and multipart data. Typically that's not an issue
|
||||
* since DEBUG and TRACE are intended for development, but this property may
|
||||
* be used to explicitly disable any logging of such information regardless
|
||||
* of the log level.
|
||||
* <p>By default this is set to {@code false} in which case values encoded
|
||||
* or decoded are logged at DEBUG level. When set to {@code true} values
|
||||
* will not be logged at any level.
|
||||
* @param disableLoggingRequestDetails whether to disable
|
||||
* Whether to log form data at DEBUG level, and headers at TRACE level.
|
||||
* Both may contain sensitive information.
|
||||
* <p>By default set to {@code false} so that request details are not shown.
|
||||
* @param enable whether to enable or not
|
||||
*/
|
||||
public void setDisableLoggingRequestDetails(boolean disableLoggingRequestDetails) {
|
||||
this.disableLoggingRequestDetails = disableLoggingRequestDetails;
|
||||
public void setEnableLoggingRequestDetails(boolean enable) {
|
||||
this.enableLoggingRequestDetails = enable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether any logging of values being encoded or decoded is explicitly
|
||||
* disabled regardless of log level.
|
||||
*/
|
||||
public boolean isDisableLoggingRequestDetails() {
|
||||
return this.disableLoggingRequestDetails;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns "true" if logger is at DEBUG level and the logging of values
|
||||
* being encoded or decoded is not explicitly disabled.
|
||||
*/
|
||||
protected boolean shouldLogRequestDetails() {
|
||||
return !this.disableLoggingRequestDetails && logger.isDebugEnabled();
|
||||
public boolean isEnableLoggingRequestDetails() {
|
||||
return this.enableLoggingRequestDetails;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ public class ResourceHttpMessageWriter implements HttpMessageWriter<Resource> {
|
||||
return mediaType;
|
||||
}
|
||||
mediaType = MediaTypeFactory.getMediaType(resource).orElse(MediaType.APPLICATION_OCTET_STREAM);
|
||||
if (logger.isDebugEnabled()) {
|
||||
if (logger.isDebugEnabled() && !Hints.isLoggingSuppressed(hints)) {
|
||||
logger.debug(Hints.getLogPrefix(hints) + "Resource associated with '" + mediaType + "'");
|
||||
}
|
||||
return mediaType;
|
||||
|
||||
@@ -112,7 +112,7 @@ public abstract class AbstractJackson2Decoder extends Jackson2CodecSupport imple
|
||||
return tokens.map(tokenBuffer -> {
|
||||
try {
|
||||
Object value = reader.readValue(tokenBuffer.asParser(getObjectMapper()));
|
||||
if (logger.isDebugEnabled()) {
|
||||
if (logger.isDebugEnabled() && !Hints.isLoggingSuppressed(hints)) {
|
||||
logger.debug(Hints.getLogPrefix(hints) +"Decoded [" + value + "]");
|
||||
}
|
||||
return value;
|
||||
|
||||
@@ -141,7 +141,7 @@ public abstract class AbstractJackson2Encoder extends Jackson2CodecSupport imple
|
||||
private DataBuffer encodeValue(Object value, @Nullable MimeType mimeType, DataBufferFactory bufferFactory,
|
||||
ResolvableType elementType, @Nullable Map<String, Object> hints, JsonEncoding encoding) {
|
||||
|
||||
if (logger.isDebugEnabled() && !Hints.suppressLogging(hints)) {
|
||||
if (logger.isDebugEnabled() && !Hints.isLoggingSuppressed(hints)) {
|
||||
logger.debug(Hints.getLogPrefix(hints) + "Encoding [" + value + "]");
|
||||
}
|
||||
|
||||
|
||||
@@ -27,9 +27,11 @@ import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.codec.Hints;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ReactiveHttpInputMessage;
|
||||
import org.springframework.http.codec.HttpMessageReader;
|
||||
import org.springframework.http.codec.LoggingCodecSupport;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
@@ -46,7 +48,8 @@ import org.springframework.util.MultiValueMap;
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 5.0
|
||||
*/
|
||||
public class MultipartHttpMessageReader implements HttpMessageReader<MultiValueMap<String, Part>> {
|
||||
public class MultipartHttpMessageReader extends LoggingCodecSupport
|
||||
implements HttpMessageReader<MultiValueMap<String, Part>> {
|
||||
|
||||
private static final ResolvableType MULTIPART_VALUE_TYPE = ResolvableType.forClassWithGenerics(
|
||||
MultiValueMap.class, String.class, Part.class);
|
||||
@@ -85,8 +88,19 @@ public class MultipartHttpMessageReader implements HttpMessageReader<MultiValueM
|
||||
public Mono<MultiValueMap<String, Part>> readMono(ResolvableType elementType,
|
||||
ReactiveHttpInputMessage inputMessage, Map<String, Object> hints) {
|
||||
|
||||
return this.partReader.read(elementType, inputMessage, hints)
|
||||
.collectMultimap(Part::name).map(this::toMultiValueMap);
|
||||
|
||||
Map<String, Object> allHints = Hints.merge(hints, Hints.SUPPRESS_LOGGING_HINT, true);
|
||||
|
||||
return this.partReader.read(elementType, inputMessage, allHints)
|
||||
.collectMultimap(Part::name)
|
||||
.doOnNext(map -> {
|
||||
if (logger.isDebugEnabled()) {
|
||||
String details = isEnableLoggingRequestDetails() ?
|
||||
map.toString() : "parts " + map.keySet() + " (content masked)";
|
||||
logger.debug(Hints.getLogPrefix(hints) + "Parsed " + details);
|
||||
}
|
||||
})
|
||||
.map(this::toMultiValueMap);
|
||||
}
|
||||
|
||||
private LinkedMultiValueMap<String, Part> toMultiValueMap(Map<String, Collection<Part>> map) {
|
||||
|
||||
@@ -225,8 +225,10 @@ public class MultipartHttpMessageWriter extends LoggingCodecSupport
|
||||
|
||||
outputMessage.getHeaders().setContentType(new MediaType(MediaType.MULTIPART_FORM_DATA, params));
|
||||
|
||||
if (shouldLogRequestDetails()) {
|
||||
logger.debug(Hints.getLogPrefix(hints) + "Encoding " + map);
|
||||
if (logger.isDebugEnabled()) {
|
||||
String details = isEnableLoggingRequestDetails() ?
|
||||
map.toString() : "parts " + map.keySet() + " (content masked)";
|
||||
logger.debug(Hints.getLogPrefix(hints) + "Encoding " + details);
|
||||
}
|
||||
|
||||
Flux<DataBuffer> body = Flux.fromIterable(map.entrySet())
|
||||
|
||||
@@ -95,8 +95,10 @@ public class SynchronossPartHttpMessageReader extends LoggingCodecSupport implem
|
||||
public Flux<Part> read(ResolvableType elementType, ReactiveHttpInputMessage message, Map<String, Object> hints) {
|
||||
return Flux.create(new SynchronossPartGenerator(message, this.bufferFactory, this.streamStorageFactory))
|
||||
.doOnNext(part -> {
|
||||
if (shouldLogRequestDetails()) {
|
||||
logger.debug(Hints.getLogPrefix(hints) + "Decoded [" + part + "]");
|
||||
if (logger.isDebugEnabled() && !Hints.isLoggingSuppressed(hints)) {
|
||||
String details = isEnableLoggingRequestDetails() ?
|
||||
part.toString() : "parts '" + part.name() + "' (content masked)";
|
||||
logger.debug(Hints.getLogPrefix(hints) + "Parsed " + details);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ class BaseDefaultCodecs implements CodecConfigurer.DefaultCodecs {
|
||||
@Nullable
|
||||
private Encoder<?> jackson2JsonEncoder;
|
||||
|
||||
private boolean disableLoggingRequestDetails = false;
|
||||
private boolean enableLoggingRequestDetails = false;
|
||||
|
||||
private boolean registerDefaults = true;
|
||||
|
||||
@@ -90,12 +90,12 @@ class BaseDefaultCodecs implements CodecConfigurer.DefaultCodecs {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disableLoggingRequestDetails(boolean disableLoggingRequestDetails) {
|
||||
this.disableLoggingRequestDetails = disableLoggingRequestDetails;
|
||||
public void enableLoggingRequestDetails(boolean enable) {
|
||||
this.enableLoggingRequestDetails = enable;
|
||||
}
|
||||
|
||||
protected boolean isDisableLoggingRequestDetails() {
|
||||
return this.disableLoggingRequestDetails;
|
||||
protected boolean isEnableLoggingRequestDetails() {
|
||||
return this.enableLoggingRequestDetails;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -121,7 +121,7 @@ class BaseDefaultCodecs implements CodecConfigurer.DefaultCodecs {
|
||||
readers.add(new DecoderHttpMessageReader<>(StringDecoder.textPlainOnly()));
|
||||
|
||||
FormHttpMessageReader formReader = new FormHttpMessageReader();
|
||||
formReader.setDisableLoggingRequestDetails(this.disableLoggingRequestDetails);
|
||||
formReader.setEnableLoggingRequestDetails(this.enableLoggingRequestDetails);
|
||||
readers.add(formReader);
|
||||
|
||||
extendTypedReaders(readers);
|
||||
|
||||
@@ -88,10 +88,10 @@ class ClientDefaultCodecsImpl extends BaseDefaultCodecs implements ClientCodecCo
|
||||
protected void extendTypedWriters(List<HttpMessageWriter<?>> typedWriters) {
|
||||
|
||||
FormHttpMessageWriter formWriter = new FormHttpMessageWriter();
|
||||
formWriter.setDisableLoggingRequestDetails(isDisableLoggingRequestDetails());
|
||||
formWriter.setEnableLoggingRequestDetails(isEnableLoggingRequestDetails());
|
||||
|
||||
MultipartHttpMessageWriter multipartWriter = new MultipartHttpMessageWriter(getPartWriters(), formWriter);
|
||||
multipartWriter.setDisableLoggingRequestDetails(isDisableLoggingRequestDetails());
|
||||
multipartWriter.setEnableLoggingRequestDetails(isEnableLoggingRequestDetails());
|
||||
|
||||
typedWriters.add(multipartWriter);
|
||||
}
|
||||
|
||||
@@ -52,10 +52,15 @@ class ServerDefaultCodecsImpl extends BaseDefaultCodecs implements ServerCodecCo
|
||||
@Override
|
||||
protected void extendTypedReaders(List<HttpMessageReader<?>> typedReaders) {
|
||||
if (synchronossMultipartPresent) {
|
||||
boolean enable = isEnableLoggingRequestDetails();
|
||||
|
||||
SynchronossPartHttpMessageReader partReader = new SynchronossPartHttpMessageReader();
|
||||
partReader.setDisableLoggingRequestDetails(isDisableLoggingRequestDetails());
|
||||
partReader.setEnableLoggingRequestDetails(enable);
|
||||
typedReaders.add(partReader);
|
||||
typedReaders.add(new MultipartHttpMessageReader(partReader));
|
||||
|
||||
MultipartHttpMessageReader reader = new MultipartHttpMessageReader(partReader);
|
||||
reader.setEnableLoggingRequestDetails(enable);
|
||||
typedReaders.add(reader);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ public class Jaxb2XmlEncoder extends AbstractSingleValueEncoder<Object> {
|
||||
protected Flux<DataBuffer> encode(Object value, DataBufferFactory dataBufferFactory,
|
||||
ResolvableType type, @Nullable MimeType mimeType, @Nullable Map<String, Object> hints) {
|
||||
try {
|
||||
if (logger.isDebugEnabled() && !Hints.suppressLogging(hints)) {
|
||||
if (logger.isDebugEnabled() && !Hints.isLoggingSuppressed(hints)) {
|
||||
logger.debug(Hints.getLogPrefix(hints) + "Encoding [" + value + "]");
|
||||
}
|
||||
DataBuffer buffer = dataBufferFactory.allocateBuffer(1024);
|
||||
|
||||
@@ -57,7 +57,7 @@ public class ReactorHttpHandlerAdapter implements BiFunction<HttpServerRequest,
|
||||
NettyDataBufferFactory bufferFactory = new NettyDataBufferFactory(reactorResponse.alloc());
|
||||
try {
|
||||
ReactorServerHttpRequest request = new ReactorServerHttpRequest(reactorRequest, bufferFactory);
|
||||
ServerHttpResponse response = new ReactorServerHttpResponse(reactorResponse, bufferFactory, request);
|
||||
ServerHttpResponse response = new ReactorServerHttpResponse(reactorResponse, bufferFactory);
|
||||
|
||||
if (request.getMethod() == HttpMethod.HEAD) {
|
||||
response = new HttpHeadResponseDecorator(response);
|
||||
|
||||
@@ -165,13 +165,7 @@ class ReactorServerHttpRequest extends AbstractServerHttpRequest {
|
||||
|
||||
@Override
|
||||
public Flux<DataBuffer> getBody() {
|
||||
return this.request.receive().retain()
|
||||
.doOnNext(buffer -> {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace(getLogPrefix() + "Read " + buffer.readableBytes() + " bytes");
|
||||
}
|
||||
})
|
||||
.map(this.bufferFactory::wrap);
|
||||
return this.request.receive().retain().map(this.bufferFactory::wrap);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
||||
@@ -45,16 +45,11 @@ class ReactorServerHttpResponse extends AbstractServerHttpResponse implements Ze
|
||||
|
||||
private final HttpServerResponse response;
|
||||
|
||||
private final ReactorServerHttpRequest request;
|
||||
|
||||
|
||||
public ReactorServerHttpResponse(HttpServerResponse response, DataBufferFactory bufferFactory,
|
||||
ReactorServerHttpRequest request) {
|
||||
|
||||
public ReactorServerHttpResponse(HttpServerResponse response, DataBufferFactory bufferFactory) {
|
||||
super(bufferFactory);
|
||||
Assert.notNull(response, "HttpServerResponse must not be null");
|
||||
this.response = response;
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
|
||||
@@ -119,12 +114,7 @@ class ReactorServerHttpResponse extends AbstractServerHttpResponse implements Ze
|
||||
}
|
||||
|
||||
private Publisher<ByteBuf> toByteBufs(Publisher<? extends DataBuffer> dataBuffers) {
|
||||
return Flux.from(dataBuffers).map(NettyDataBufferFactory::toByteBuf)
|
||||
.doOnNext(byteBuf -> {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Writing " + byteBuf.readableBytes() + " bytes");
|
||||
}
|
||||
});
|
||||
return Flux.from(dataBuffers).map(NettyDataBufferFactory::toByteBuf);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -199,15 +199,7 @@ class ServletServerHttpRequest extends AbstractServerHttpRequest {
|
||||
@Nullable
|
||||
DataBuffer readFromInputStream() throws IOException {
|
||||
int read = this.request.getInputStream().read(this.buffer);
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace(getLogPrefix() + "Read " + read + (read != -1 ? " bytes" : ""));
|
||||
}
|
||||
else {
|
||||
Log rsReadLogger = AbstractListenerReadPublisher.rsReadLogger;
|
||||
if (rsReadLogger.isTraceEnabled()) {
|
||||
rsReadLogger.trace(getLogPrefix() + "Read " + read + (read != -1 ? " bytes" : ""));
|
||||
}
|
||||
}
|
||||
logBytesRead(read);
|
||||
|
||||
if (read > 0) {
|
||||
DataBuffer dataBuffer = this.bufferFactory.allocateBuffer(read);
|
||||
@@ -222,6 +214,13 @@ class ServletServerHttpRequest extends AbstractServerHttpRequest {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected final void logBytesRead(int read) {
|
||||
Log rsReadLogger = AbstractListenerReadPublisher.rsReadLogger;
|
||||
if (rsReadLogger.isTraceEnabled()) {
|
||||
rsReadLogger.trace(getLogPrefix() + "Read " + read + (read != -1 ? " bytes" : ""));
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T> T getNativeRequest() {
|
||||
|
||||
@@ -86,9 +86,7 @@ public class TomcatHttpHandlerAdapter extends ServletHttpHandlerAdapter {
|
||||
|
||||
ServletRequest request = getNativeRequest();
|
||||
int read = ((CoyoteInputStream) request.getInputStream()).read(byteBuffer);
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace(getLogPrefix() + "read:" + read);
|
||||
}
|
||||
logBytesRead(read);
|
||||
|
||||
if (read > 0) {
|
||||
dataBuffer.writePosition(read);
|
||||
|
||||
@@ -31,7 +31,6 @@ import io.undertow.connector.PooledByteBuffer;
|
||||
import io.undertow.server.HttpServerExchange;
|
||||
import io.undertow.server.handlers.Cookie;
|
||||
import io.undertow.util.HeaderValues;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.xnio.channels.StreamSourceChannel;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
@@ -182,11 +181,7 @@ class UndertowServerHttpRequest extends AbstractServerHttpRequest {
|
||||
ByteBuffer byteBuffer = pooledByteBuffer.getBuffer();
|
||||
int read = this.channel.read(byteBuffer);
|
||||
|
||||
Log logger = UndertowServerHttpRequest.this.logger;
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace(getLogPrefix() + "Read " + read + (read != -1 ? " bytes" : ""));
|
||||
}
|
||||
else if (rsReadLogger.isTraceEnabled()) {
|
||||
if (rsReadLogger.isTraceEnabled()) {
|
||||
rsReadLogger.trace(getLogPrefix() + "Read " + read + (read != -1 ? " bytes" : ""));
|
||||
}
|
||||
|
||||
|
||||
@@ -121,18 +121,21 @@ public class DefaultServerWebExchange implements ServerWebExchange {
|
||||
Assert.notNull(codecConfigurer, "'codecConfigurer' is required");
|
||||
Assert.notNull(localeContextResolver, "'localeContextResolver' is required");
|
||||
|
||||
// Initialize before first call to getLogPrefix()
|
||||
this.attributes.put(ServerWebExchange.LOG_ID_ATTRIBUTE, request.getId());
|
||||
|
||||
this.request = request;
|
||||
this.response = response;
|
||||
this.sessionMono = sessionManager.getSession(this).cache();
|
||||
this.localeContextResolver = localeContextResolver;
|
||||
this.formDataMono = initFormData(request, codecConfigurer);
|
||||
this.multipartDataMono = initMultipartData(request, codecConfigurer);
|
||||
this.formDataMono = initFormData(request, codecConfigurer, getLogPrefix());
|
||||
this.multipartDataMono = initMultipartData(request, codecConfigurer, getLogPrefix());
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static Mono<MultiValueMap<String, String>> initFormData(ServerHttpRequest request,
|
||||
ServerCodecConfigurer configurer) {
|
||||
ServerCodecConfigurer configurer, String logPrefix) {
|
||||
|
||||
try {
|
||||
MediaType contentType = request.getHeaders().getContentType();
|
||||
@@ -141,7 +144,7 @@ public class DefaultServerWebExchange implements ServerWebExchange {
|
||||
.filter(reader -> reader.canRead(FORM_DATA_TYPE, MediaType.APPLICATION_FORM_URLENCODED))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new IllegalStateException("No form data HttpMessageReader.")))
|
||||
.readMono(FORM_DATA_TYPE, request, Hints.none())
|
||||
.readMono(FORM_DATA_TYPE, request, Hints.from(Hints.LOG_PREFIX_HINT, logPrefix))
|
||||
.switchIfEmpty(EMPTY_FORM_DATA)
|
||||
.cache();
|
||||
}
|
||||
@@ -154,7 +157,7 @@ public class DefaultServerWebExchange implements ServerWebExchange {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static Mono<MultiValueMap<String, Part>> initMultipartData(ServerHttpRequest request,
|
||||
ServerCodecConfigurer configurer) {
|
||||
ServerCodecConfigurer configurer, String logPrefix) {
|
||||
|
||||
try {
|
||||
MediaType contentType = request.getHeaders().getContentType();
|
||||
@@ -163,7 +166,7 @@ public class DefaultServerWebExchange implements ServerWebExchange {
|
||||
.filter(reader -> reader.canRead(MULTIPART_DATA_TYPE, MediaType.MULTIPART_FORM_DATA))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new IllegalStateException("No multipart HttpMessageReader.")))
|
||||
.readMono(MULTIPART_DATA_TYPE, request, Hints.none())
|
||||
.readMono(MULTIPART_DATA_TYPE, request, Hints.from(Hints.LOG_PREFIX_HINT, logPrefix))
|
||||
.switchIfEmpty(EMPTY_MULTIPART_DATA)
|
||||
.cache();
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.NestedExceptionUtils;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.codec.LoggingCodecSupport;
|
||||
import org.springframework.http.codec.ServerCodecConfigurer;
|
||||
@@ -94,8 +95,8 @@ public class HttpWebHandlerAdapter extends WebHandlerDecorator implements HttpHa
|
||||
@Nullable
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
/** Do not log potentially sensitive data (query/form at DEBUG, headers at TRACE). */
|
||||
private boolean disableLoggingRequestDetails = false;
|
||||
/** Whether to log potentially sensitive info (form data at DEBUG, headers at TRACE). */
|
||||
private boolean enableLoggingRequestDetails = false;
|
||||
|
||||
|
||||
public HttpWebHandlerAdapter(WebHandler delegate) {
|
||||
@@ -132,12 +133,12 @@ public class HttpWebHandlerAdapter extends WebHandlerDecorator implements HttpHa
|
||||
Assert.notNull(codecConfigurer, "ServerCodecConfigurer is required");
|
||||
this.codecConfigurer = codecConfigurer;
|
||||
|
||||
this.disableLoggingRequestDetails = false;
|
||||
this.enableLoggingRequestDetails = false;
|
||||
this.codecConfigurer.getReaders().stream()
|
||||
.filter(LoggingCodecSupport.class::isInstance)
|
||||
.forEach(reader -> {
|
||||
if (((LoggingCodecSupport) reader).isDisableLoggingRequestDetails()) {
|
||||
this.disableLoggingRequestDetails = true;
|
||||
if (((LoggingCodecSupport) reader).isEnableLoggingRequestDetails()) {
|
||||
this.enableLoggingRequestDetails = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -195,17 +196,11 @@ public class HttpWebHandlerAdapter extends WebHandlerDecorator implements HttpHa
|
||||
*/
|
||||
public void afterPropertiesSet() {
|
||||
if (logger.isDebugEnabled()) {
|
||||
if (this.disableLoggingRequestDetails) {
|
||||
logger.debug("Logging query, form data, multipart data, and headers is OFF.");
|
||||
}
|
||||
else {
|
||||
logger.warn("\n\n" +
|
||||
"!!!!!!!!!!!!!!!!!!!\n" +
|
||||
"Logging query, form and multipart data (DEBUG), and headers (TRACE) may show sensitive data.\n" +
|
||||
"If not in development, set \"disableLoggingRequestDetails(true)\" on ServerCodecConfigurer,\n" +
|
||||
"or lower the log level.\n" +
|
||||
"!!!!!!!!!!!!!!!!!!!\n");
|
||||
}
|
||||
String value = this.enableLoggingRequestDetails ?
|
||||
"shown which may lead to unsafe logging of potentially sensitive data" :
|
||||
"masked to prevent unsafe logging of potentially sensitive data";
|
||||
logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
|
||||
"': form data and headers will be " + value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,7 +209,6 @@ public class HttpWebHandlerAdapter extends WebHandlerDecorator implements HttpHa
|
||||
public Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response) {
|
||||
|
||||
ServerWebExchange exchange = createExchange(request, response);
|
||||
exchange.getAttributes().put(ServerWebExchange.LOG_ID_ATTRIBUTE, request.getId());
|
||||
logExchange(exchange);
|
||||
|
||||
return getDelegate().handle(exchange)
|
||||
@@ -233,8 +227,7 @@ public class HttpWebHandlerAdapter extends WebHandlerDecorator implements HttpHa
|
||||
String logPrefix = exchange.getLogPrefix();
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
if (logger.isTraceEnabled()) {
|
||||
String headers = this.disableLoggingRequestDetails ? "" : ", headers=" + request.getHeaders();
|
||||
logger.trace(logPrefix + formatRequest(request) + headers);
|
||||
logger.trace(logPrefix + formatRequest(request) + ", headers=" + formatHeaders(request.getHeaders()));
|
||||
}
|
||||
else {
|
||||
logger.debug(logPrefix + formatRequest(request));
|
||||
@@ -243,11 +236,8 @@ public class HttpWebHandlerAdapter extends WebHandlerDecorator implements HttpHa
|
||||
}
|
||||
|
||||
private String formatRequest(ServerHttpRequest request) {
|
||||
String query = "";
|
||||
if (!this.disableLoggingRequestDetails) {
|
||||
String rawQuery = request.getURI().getRawQuery();
|
||||
query = StringUtils.hasText(rawQuery) ? "?" + rawQuery : "";
|
||||
}
|
||||
String rawQuery = request.getURI().getRawQuery();
|
||||
String query = StringUtils.hasText(rawQuery) ? "?" + rawQuery : "";
|
||||
return "HTTP " + request.getMethod() + " \"" + request.getPath() + query + "\"";
|
||||
}
|
||||
|
||||
@@ -258,8 +248,7 @@ public class HttpWebHandlerAdapter extends WebHandlerDecorator implements HttpHa
|
||||
HttpStatus status = response.getStatusCode();
|
||||
String message = "Completed " + (status != null ? status : "200 OK");
|
||||
if (logger.isTraceEnabled()) {
|
||||
String headers = this.disableLoggingRequestDetails ? "" : ", headers=" + response.getHeaders();
|
||||
logger.trace(logPrefix + message + headers);
|
||||
logger.trace(logPrefix + message + ", headers=" + formatHeaders(response.getHeaders()));
|
||||
}
|
||||
else {
|
||||
logger.debug(logPrefix + message);
|
||||
@@ -267,6 +256,11 @@ public class HttpWebHandlerAdapter extends WebHandlerDecorator implements HttpHa
|
||||
}
|
||||
}
|
||||
|
||||
private String formatHeaders(HttpHeaders responseHeaders) {
|
||||
return this.enableLoggingRequestDetails ?
|
||||
responseHeaders.toString() : responseHeaders.isEmpty() ? "{}" : "{masked}";
|
||||
}
|
||||
|
||||
private Mono<Void> handleUnresolvedError(ServerWebExchange exchange, Throwable ex) {
|
||||
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
|
||||
Reference in New Issue
Block a user