diff --git a/core-common/src/main/java/org/glassfish/jersey/logging/ClientLoggingFilter.java b/core-common/src/main/java/org/glassfish/jersey/logging/ClientLoggingFilter.java index 07c3dbeca6..b250915db6 100644 --- a/core-common/src/main/java/org/glassfish/jersey/logging/ClientLoggingFilter.java +++ b/core-common/src/main/java/org/glassfish/jersey/logging/ClientLoggingFilter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -64,6 +64,7 @@ final class ClientLoggingFilter extends LoggingInterceptor implements ClientRequ * logging filter will print (and buffer in memory) only the specified number of bytes * and print "...more..." string at the end. Negative values are interpreted as zero. * separator delimiter for particular log lines. Default is Linux new line delimiter + * redactHeaders a collection of HTTP headers to be redacted when logging. */ public ClientLoggingFilter(LoggingFeature.LoggingFeatureBuilder builder) { super(builder); diff --git a/core-common/src/main/java/org/glassfish/jersey/logging/LoggingFeature.java b/core-common/src/main/java/org/glassfish/jersey/logging/LoggingFeature.java index f9b4d51a5d..214eaa2dfc 100644 --- a/core-common/src/main/java/org/glassfish/jersey/logging/LoggingFeature.java +++ b/core-common/src/main/java/org/glassfish/jersey/logging/LoggingFeature.java @@ -16,6 +16,8 @@ package org.glassfish.jersey.logging; +import java.util.Arrays; +import java.util.Collection; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; @@ -23,6 +25,7 @@ import javax.ws.rs.RuntimeType; import javax.ws.rs.core.Feature; import javax.ws.rs.core.FeatureContext; +import javax.ws.rs.core.HttpHeaders; import org.glassfish.jersey.CommonProperties; @@ -41,6 +44,7 @@ *
  • {@link #LOGGING_FEATURE_VERBOSITY}
  • *
  • {@link #LOGGING_FEATURE_MAX_ENTITY_SIZE}
  • *
  • {@link #LOGGING_FEATURE_SEPARATOR}
  • + *
  • {@link #LOGGING_FEATURE_REDACT_HEADERS}
  • * *

    * If any of the configuration value is not set, following default values are applied: @@ -50,6 +54,7 @@ *

  • verbosity: {@link Verbosity#PAYLOAD_TEXT}
  • *
  • maximum entity size: {@value #DEFAULT_MAX_ENTITY_SIZE}
  • *
  • line separator: {@link #DEFAULT_SEPARATOR}
  • + *
  • redact headers: {@value #DEFAULT_REDACT_HEADERS}
  • * *

    * Server configurable properties: @@ -59,6 +64,7 @@ *

  • {@link #LOGGING_FEATURE_VERBOSITY_SERVER}
  • *
  • {@link #LOGGING_FEATURE_MAX_ENTITY_SIZE_SERVER}
  • *
  • {@link #LOGGING_FEATURE_SEPARATOR_SERVER}
  • + *
  • {@link #LOGGING_FEATURE_REDACT_HEADERS_SERVER}
  • * * Client configurable properties: * * * @author Ondrej Kosatka @@ -94,12 +101,17 @@ public class LoggingFeature implements Feature { * Default separator for entity logging. */ public static final String DEFAULT_SEPARATOR = "\n"; + /** + * Default headers to be redacted. If multiple, separate each header with a semicolon. + */ + public static final String DEFAULT_REDACT_HEADERS = HttpHeaders.AUTHORIZATION; private static final String LOGGER_NAME_POSTFIX = ".logger.name"; private static final String LOGGER_LEVEL_POSTFIX = ".logger.level"; private static final String VERBOSITY_POSTFIX = ".verbosity"; private static final String MAX_ENTITY_POSTFIX = ".entity.maxSize"; private static final String SEPARATOR_POSTFIX = ".separator"; + private static final String REDACT_HEADERS_POSTFIX = ".headers.redact"; private static final String LOGGING_FEATURE_COMMON_PREFIX = "jersey.config.logging"; /** * Common logger name property. @@ -121,6 +133,10 @@ public class LoggingFeature implements Feature { * Common property for configuring logging separator. */ public static final String LOGGING_FEATURE_SEPARATOR = LOGGING_FEATURE_COMMON_PREFIX + SEPARATOR_POSTFIX; + /** + * Common property for configuring headers to be redacted. The headers are semicolon-separated. + */ + public static final String LOGGING_FEATURE_REDACT_HEADERS = LOGGING_FEATURE_COMMON_PREFIX + REDACT_HEADERS_POSTFIX; private static final String LOGGING_FEATURE_SERVER_PREFIX = "jersey.config.server.logging"; /** @@ -143,6 +159,11 @@ public class LoggingFeature implements Feature { * Server property for configuring separator. */ public static final String LOGGING_FEATURE_SEPARATOR_SERVER = LOGGING_FEATURE_SERVER_PREFIX + SEPARATOR_POSTFIX; + /** + * Server property for configuring headers to be redacted. The headers are semicolon-separated. + */ + public static final String LOGGING_FEATURE_REDACT_HEADERS_SERVER = + LOGGING_FEATURE_SERVER_PREFIX + REDACT_HEADERS_POSTFIX; private static final String LOGGING_FEATURE_CLIENT_PREFIX = "jersey.config.client.logging"; /** @@ -165,6 +186,11 @@ public class LoggingFeature implements Feature { * Client property for logging separator. */ public static final String LOGGING_FEATURE_SEPARATOR_CLIENT = LOGGING_FEATURE_CLIENT_PREFIX + SEPARATOR_POSTFIX; + /** + * Client property for configuring headers to be redacted. The headers are semicolon-separated. + */ + public static final String LOGGING_FEATURE_REDACT_HEADERS_CLIENT = + LOGGING_FEATURE_CLIENT_PREFIX + REDACT_HEADERS_POSTFIX; private final LoggingFeatureBuilder builder; @@ -269,7 +295,7 @@ private LoggingInterceptor createLoggingFilter(FeatureContext context, RuntimeTy private static LoggingFeatureBuilder configureBuilderParameters(LoggingFeatureBuilder builder, FeatureContext context, RuntimeType runtimeType) { - final Map properties = context.getConfiguration().getProperties(); + final Map properties = context.getConfiguration().getProperties(); //get values from properties (if any) final String filterLoggerName = CommonProperties.getValue( properties, @@ -283,14 +309,14 @@ private static LoggingFeatureBuilder configureBuilderParameters(LoggingFeatureBu properties, runtimeType == RuntimeType.SERVER ? LOGGING_FEATURE_LOGGER_LEVEL_SERVER : LOGGING_FEATURE_LOGGER_LEVEL_CLIENT, CommonProperties.getValue( - context.getConfiguration().getProperties(), + properties, LOGGING_FEATURE_LOGGER_LEVEL, DEFAULT_LOGGER_LEVEL)); final String filterSeparator = CommonProperties.getValue( properties, runtimeType == RuntimeType.SERVER ? LOGGING_FEATURE_SEPARATOR_SERVER : LOGGING_FEATURE_SEPARATOR_CLIENT, CommonProperties.getValue( - context.getConfiguration().getProperties(), + properties, LOGGING_FEATURE_SEPARATOR, DEFAULT_SEPARATOR)); final Verbosity filterVerbosity = CommonProperties.getValue( @@ -310,6 +336,14 @@ private static LoggingFeatureBuilder configureBuilderParameters(LoggingFeatureBu LOGGING_FEATURE_MAX_ENTITY_SIZE, DEFAULT_MAX_ENTITY_SIZE )); + final String redactHeaders = CommonProperties.getValue( + properties, + runtimeType == RuntimeType.SERVER + ? LOGGING_FEATURE_REDACT_HEADERS_SERVER : LOGGING_FEATURE_REDACT_HEADERS_CLIENT, + CommonProperties.getValue( + properties, + LOGGING_FEATURE_REDACT_HEADERS, + DEFAULT_REDACT_HEADERS)); final Level loggerLevel = Level.parse(filterLevel); @@ -319,6 +353,8 @@ private static LoggingFeatureBuilder configureBuilderParameters(LoggingFeatureBu builder.maxEntitySize = builder.maxEntitySize == null ? filterMaxEntitySize : builder.maxEntitySize; builder.level = builder.level == null ? loggerLevel : builder.level; builder.separator = builder.separator == null ? filterSeparator : builder.separator; + builder.redactHeaders = builder.redactHeaders == null + ? Arrays.asList(redactHeaders.split(";")) : builder.redactHeaders; return builder; } @@ -376,6 +412,7 @@ public static class LoggingFeatureBuilder { Integer maxEntitySize; Level level; String separator; + Collection redactHeaders; public LoggingFeatureBuilder() { @@ -400,9 +437,13 @@ public LoggingFeatureBuilder separator(String separator) { this.separator = separator; return this; } + public LoggingFeatureBuilder redactHeaders(Collection redactHeaders) { + this.redactHeaders = redactHeaders; + return this; + } public LoggingFeature build() { return new LoggingFeature(this); } } -} \ No newline at end of file +} diff --git a/core-common/src/main/java/org/glassfish/jersey/logging/LoggingFeatureAutoDiscoverable.java b/core-common/src/main/java/org/glassfish/jersey/logging/LoggingFeatureAutoDiscoverable.java index 01e61a5e9d..84ca349bbd 100644 --- a/core-common/src/main/java/org/glassfish/jersey/logging/LoggingFeatureAutoDiscoverable.java +++ b/core-common/src/main/java/org/glassfish/jersey/logging/LoggingFeatureAutoDiscoverable.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -34,6 +34,9 @@ import static org.glassfish.jersey.logging.LoggingFeature.LOGGING_FEATURE_MAX_ENTITY_SIZE; import static org.glassfish.jersey.logging.LoggingFeature.LOGGING_FEATURE_MAX_ENTITY_SIZE_CLIENT; import static org.glassfish.jersey.logging.LoggingFeature.LOGGING_FEATURE_MAX_ENTITY_SIZE_SERVER; +import static org.glassfish.jersey.logging.LoggingFeature.LOGGING_FEATURE_REDACT_HEADERS; +import static org.glassfish.jersey.logging.LoggingFeature.LOGGING_FEATURE_REDACT_HEADERS_CLIENT; +import static org.glassfish.jersey.logging.LoggingFeature.LOGGING_FEATURE_REDACT_HEADERS_SERVER; import static org.glassfish.jersey.logging.LoggingFeature.LOGGING_FEATURE_SEPARATOR; import static org.glassfish.jersey.logging.LoggingFeature.LOGGING_FEATURE_SEPARATOR_CLIENT; import static org.glassfish.jersey.logging.LoggingFeature.LOGGING_FEATURE_SEPARATOR_SERVER; @@ -75,7 +78,8 @@ private boolean commonPropertyConfigured(Map properties) { || properties.containsKey(LOGGING_FEATURE_LOGGER_LEVEL) || properties.containsKey(LOGGING_FEATURE_VERBOSITY) || properties.containsKey(LOGGING_FEATURE_MAX_ENTITY_SIZE) - || properties.containsKey(LOGGING_FEATURE_SEPARATOR); + || properties.containsKey(LOGGING_FEATURE_SEPARATOR) + || properties.containsKey(LOGGING_FEATURE_REDACT_HEADERS); } private boolean clientConfigured(Map properties) { @@ -83,7 +87,8 @@ private boolean clientConfigured(Map properties) { || properties.containsKey(LOGGING_FEATURE_LOGGER_LEVEL_CLIENT) || properties.containsKey(LOGGING_FEATURE_VERBOSITY_CLIENT) || properties.containsKey(LOGGING_FEATURE_MAX_ENTITY_SIZE_CLIENT) - || properties.containsKey(LOGGING_FEATURE_SEPARATOR_CLIENT); + || properties.containsKey(LOGGING_FEATURE_SEPARATOR_CLIENT) + || properties.containsKey(LOGGING_FEATURE_REDACT_HEADERS_CLIENT); } private boolean serverConfigured(Map properties) { @@ -91,6 +96,7 @@ private boolean serverConfigured(Map properties) { || properties.containsKey(LOGGING_FEATURE_LOGGER_LEVEL_SERVER) || properties.containsKey(LOGGING_FEATURE_VERBOSITY_SERVER) || properties.containsKey(LOGGING_FEATURE_MAX_ENTITY_SIZE_SERVER) - || properties.containsKey(LOGGING_FEATURE_SEPARATOR_SERVER); + || properties.containsKey(LOGGING_FEATURE_SEPARATOR_SERVER) + || properties.containsKey(LOGGING_FEATURE_REDACT_HEADERS_SERVER); } } diff --git a/core-common/src/main/java/org/glassfish/jersey/logging/LoggingInterceptor.java b/core-common/src/main/java/org/glassfish/jersey/logging/LoggingInterceptor.java index fd1791b7d8..ec152508c9 100644 --- a/core-common/src/main/java/org/glassfish/jersey/logging/LoggingInterceptor.java +++ b/core-common/src/main/java/org/glassfish/jersey/logging/LoggingInterceptor.java @@ -24,15 +24,21 @@ import java.io.OutputStream; import java.net.URI; import java.nio.charset.Charset; +import java.util.Collection; import java.util.Comparator; import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.TreeSet; import java.util.concurrent.atomic.AtomicLong; +import java.util.function.BiConsumer; +import java.util.function.Predicate; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.stream.Collectors; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.MediaType; @@ -40,6 +46,7 @@ import javax.ws.rs.ext.WriterInterceptor; import javax.ws.rs.ext.WriterInterceptorContext; +import org.glassfish.jersey.internal.guava.Predicates; import org.glassfish.jersey.logging.LoggingFeature.Verbosity; import org.glassfish.jersey.message.MessageUtils; @@ -104,6 +111,7 @@ public int compare(final Map.Entry> o1, final Map.Entry redactHeaderPredicate; /** * Creates a logging filter using builder instance with custom logger and entity logging turned on, @@ -117,6 +125,7 @@ public int compare(final Map.Entry> o1, final Map.Entry> o1, final Map.Entry false; } /** @@ -169,20 +181,28 @@ void printPrefixedHeaders(final StringBuilder b, final List val = headerEntry.getValue(); final String header = headerEntry.getKey(); - if (val.size() == 1) { - prefixId(b, id).append(prefix).append(header).append(": ").append(val.get(0)).append(separator); - } else { - final StringBuilder sb = new StringBuilder(); + prefixId(b, id).append(prefix).append(header).append(": "); + getValuesAppender(header, val).accept(b, val); + b.append(separator); + } + } + + private BiConsumer> getValuesAppender(String header, List values) { + if (redactHeaderPredicate.test(header)) { + return (b, v) -> b.append("[redacted]"); + } else if (values.size() == 1) { + return (b, v) -> b.append(v.get(0)); + } else { + return (b, v) -> { boolean add = false; - for (final Object s : val) { + for (final Object s : v) { if (add) { - sb.append(','); + b.append(','); } add = true; - sb.append(s); + b.append(s); } - prefixId(b, id).append(prefix).append(header).append(": ").append(sb.toString()).append(separator); - } + }; } } @@ -312,4 +332,24 @@ public void write(byte[] ba, int off, int len) throws IOException { } } + private static final class RedactHeaderPredicate implements Predicate { + private final Set headersToRedact; + + RedactHeaderPredicate(Collection headersToRedact) { + this.headersToRedact = headersToRedact.stream() + .filter(Objects::nonNull) + .filter(Predicates.not(String::isEmpty)) + .map(RedactHeaderPredicate::normalize) + .collect(Collectors.toSet()); + } + + @Override + public boolean test(String header) { + return headersToRedact.contains(normalize(header)); + } + + private static String normalize(String input) { + return input.trim().toLowerCase(Locale.ROOT); + } + } } diff --git a/core-common/src/main/java/org/glassfish/jersey/logging/ServerLoggingFilter.java b/core-common/src/main/java/org/glassfish/jersey/logging/ServerLoggingFilter.java index b1c2c7b393..87cac240c1 100644 --- a/core-common/src/main/java/org/glassfish/jersey/logging/ServerLoggingFilter.java +++ b/core-common/src/main/java/org/glassfish/jersey/logging/ServerLoggingFilter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2022 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -64,6 +64,7 @@ final class ServerLoggingFilter extends LoggingInterceptor implements ContainerR * logging filter will print (and buffer in memory) only the specified number of bytes * and print "...more..." string at the end. Negative values are interpreted as zero. * separator delimiter for particular log lines. Default is Linux new line delimiter + * redactHeaders a collection of HTTP headers to be redacted when logging. */ public ServerLoggingFilter(final LoggingFeature.LoggingFeatureBuilder builder) { super(builder); diff --git a/docs/src/main/docbook/appendix-properties.xml b/docs/src/main/docbook/appendix-properties.xml index 5c3455ed12..c154cd29f2 100644 --- a/docs/src/main/docbook/appendix-properties.xml +++ b/docs/src/main/docbook/appendix-properties.xml @@ -1,7 +1,7 @@