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:
*
@@ -67,6 +73,7 @@
* - {@link #LOGGING_FEATURE_VERBOSITY_CLIENT}
* - {@link #LOGGING_FEATURE_MAX_ENTITY_SIZE_CLIENT}
* - {@link #LOGGING_FEATURE_SEPARATOR_CLIENT}
+ * - {@link #LOGGING_FEATURE_REDACT_HEADERS_CLIENT}
*
*
* @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 @@