[java-oidc-common] branch main updated: JCOMOIDC-160 - Add protocol message logging support to TokenResponse decoders
Codeberg
noreply at shibboleth.net
Fri Mar 13 18:47:06 UTC 2026
This is an automated email from the git hooks/post-receive script.
codeberg pushed a commit to branch main
in repository java-oidc-common.
View the commit online:
https://codeberg.org/Shibboleth/java-oidc-common/commit/65d5f2b8ebbfc5f70d63d3ce3b27d8a1949eaa68
The following commit(s) were added to refs/heads/main by this push:
new 65d5f2b8 JCOMOIDC-160 - Add protocol message logging support to TokenResponse decoders
65d5f2b8 is described below
commit 65d5f2b8ebbfc5f70d63d3ce3b27d8a1949eaa68
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Fri Mar 13 18:46:58 2026 +0000
JCOMOIDC-160 - Add protocol message logging support to TokenResponse
decoders
- Add PROTOCOL_MESSAGE support to the token and userinfo JWT decoders.
- As taken from the OpenSAML AbstractMessageEncoder, which these
decoders are not subtypes of.
https://shibboleth.atlassian.net/browse/JCOMOIDC-160
---
.../impl/AbstractJSONResponseDecoderFunction.java | 121 ++++++++++++++++++++-
.../decoding/impl/AccessTokenResponseDecoder.java | 29 ++++-
.../decoding/impl/UserInfoResponseDecoder.java | 33 +++++-
3 files changed, 174 insertions(+), 9 deletions(-)
diff --git a/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/decoding/impl/AbstractJSONResponseDecoderFunction.java b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/decoding/impl/AbstractJSONResponseDecoderFunction.java
index 0cd2fe6c..6ef4781d 100644
--- a/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/decoding/impl/AbstractJSONResponseDecoderFunction.java
+++ b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/decoding/impl/AbstractJSONResponseDecoderFunction.java
@@ -14,16 +14,27 @@
package net.shibboleth.oidc.profile.decoding.impl;
+import java.io.IOException;
+
import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.NotThreadSafe;
+import org.apache.hc.core5.http.ClassicHttpResponse;
+import org.apache.hc.core5.http.HttpException;
import org.apache.hc.core5.http.io.HttpClientResponseHandler;
+import org.slf4j.Logger;
import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.base.MoreObjects;
+import com.nimbusds.oauth2.sdk.ErrorObject;
import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
import net.shibboleth.shared.component.AbstractInitializableComponent;
import net.shibboleth.shared.component.ComponentInitializationException;
import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.primitive.StringSupport;
/**
* Abstract class for JSON based Http client response decoders.
@@ -32,12 +43,31 @@ import net.shibboleth.shared.logic.Constraint;
*
* @param <T> the return type of the function.
*/
+ at NotThreadSafe
public abstract class AbstractJSONResponseDecoderFunction<T> extends AbstractInitializableComponent
implements HttpClientResponseHandler<T>{
+ @Nonnull public static final String BASE_PROTOCOL_MESSAGE_LOGGER_CATEGORY = "PROTOCOL_MESSAGE";
+
+ /** Used to log protocol messages. */
+ @Nonnull private Logger protocolMessageLog = LoggerFactory.getLogger(BASE_PROTOCOL_MESSAGE_LOGGER_CATEGORY);
+
+ /** The configured logging sub-category for protocol messages. */
+ @Nonnull private String protocolMessageLoggerSubCategory;
+
+
/** JSON object mapper. */
@NonnullAfterInit private ObjectMapper objectMapper;
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+
+ if (objectMapper == null) {
+ throw new ComponentInitializationException("objectMapper cannot be null");
+ }
+ }
+
/**
* Set the JSON Object Mapper to use.
*
@@ -58,13 +88,92 @@ public abstract class AbstractJSONResponseDecoderFunction<T> extends AbstractIni
return objectMapper;
}
- @Override
- protected void doInitialize() throws ComponentInitializationException {
- super.doInitialize();
-
- if (objectMapper == null) {
- throw new ComponentInitializationException("objectMapper cannot be null");
+ /**
+ * Get the configured logging sub-category for protocol messages.
+ *
+ * @return the logging sub-category
+ */
+ @Nonnull protected String getProtocolMessageLoggerSubCategory() {
+ return protocolMessageLoggerSubCategory;
+ }
+
+ /**
+ * Set the configured logging sub-category for protocol messages.
+ *
+ * @param category the logging sub-category
+ */
+ protected void setProtocolMessageLoggerSubCategory(@Nullable final String category) {
+ checkSetterPreconditions();
+ protocolMessageLoggerSubCategory = StringSupport.trimOrNull(category);
+ if (protocolMessageLoggerSubCategory != null) {
+ protocolMessageLog = LoggerFactory.getLogger(BASE_PROTOCOL_MESSAGE_LOGGER_CATEGORY + "."
+ + protocolMessageLoggerSubCategory);
+ } else {
+ protocolMessageLog = LoggerFactory.getLogger(BASE_PROTOCOL_MESSAGE_LOGGER_CATEGORY);
}
}
+
+ /**
+ * Log the decoded message to the protocol message logger.
+ */
+ protected void logDecodedMessage(final T response) {
+ if (protocolMessageLog.isDebugEnabled() ){
+ final String serializedMessage = serializeMessageForLogging(response);
+ if (serializedMessage == null) {
+ return;
+ }
+
+ protocolMessageLog.debug("\n" + serializedMessage);
+ }
+ }
+
+ /**
+ * Serialize the message for logging purposes.
+ *
+ * <p>
+ * Default implementation is to return the message object's {@link #toString()},
+ * but subclasses should override if a better message-specific serialization mechanism exists.
+ * </p>
+ *
+ * @param response the response message to serialize
+ *
+ * @return the serialized message, or null if message can not be serialized
+ */
+ @Nullable protected String serializeMessageForLogging(@Nullable final T response) {
+ return response != null ? response.toString() : null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public final T handleResponse(final ClassicHttpResponse httpResponse) throws HttpException, IOException {
+ final T response = doHandleResponse(httpResponse);
+ logDecodedMessage(response);
+ return response;
+ }
+
+ /**
+ * Helper method for getting protocol message for error object.
+ *
+ * @param errorObject The error object
+ * @return The log message
+ */
+ //TODO take from the OP's ResponseUtil. Should be in commons
+ @Nullable protected static String getProtocolMessageForErrorObject(@Nullable final ErrorObject errorObject) {
+ return errorObject == null ? null : MoreObjects.toStringHelper(errorObject).omitNullValues()
+ .add("httpStatusCode", errorObject.getHTTPStatusCode())
+ .add("code", errorObject.getCode())
+ .add("description", errorObject.getDescription())
+ .add("uri", errorObject.getURI())
+ .toString();
+ }
+
+ /**
+ * Do the actual response processing and return a result. Overridden by implementation classes.
+ *
+ * @param httpResponse the http response
+ */
+ protected abstract T doHandleResponse(final ClassicHttpResponse httpResponse)
+ throws HttpException, IOException;
+
}
diff --git a/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/decoding/impl/AccessTokenResponseDecoder.java b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/decoding/impl/AccessTokenResponseDecoder.java
index 0fc96489..d63935ae 100644
--- a/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/decoding/impl/AccessTokenResponseDecoder.java
+++ b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/decoding/impl/AccessTokenResponseDecoder.java
@@ -20,6 +20,7 @@ import java.io.InputStream;
import java.util.Map;
import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.ContentType;
@@ -31,6 +32,8 @@ import org.springframework.http.MediaType;
import org.springframework.util.MimeType;
import com.fasterxml.jackson.core.type.TypeReference;
+import com.google.common.base.MoreObjects;
+import com.nimbusds.oauth2.sdk.AccessTokenResponse;
import com.nimbusds.oauth2.sdk.TokenErrorResponse;
import com.nimbusds.oauth2.sdk.TokenResponse;
import com.nimbusds.openid.connect.sdk.OIDCTokenResponse;
@@ -47,10 +50,15 @@ public class AccessTokenResponseDecoder extends AbstractJSONResponseDecoderFunct
/** Class logger. */
@Nonnull private final Logger log = LoggerFactory.getLogger(AccessTokenResponseDecoder.class);
+
+ /** Constructor.*/
+ public AccessTokenResponseDecoder() {
+ setProtocolMessageLoggerSubCategory("OAUTH2");
+ }
/** {@inheritDoc} */
@Override
- public TokenResponse handleResponse(final ClassicHttpResponse httpResponse) throws HttpException, IOException {
+ public TokenResponse doHandleResponse(final ClassicHttpResponse httpResponse) throws HttpException, IOException {
try {
if (httpResponse == null) {
log.warn("HttpResponse was null, can not process response");
@@ -99,5 +107,24 @@ public class AccessTokenResponseDecoder extends AbstractJSONResponseDecoderFunct
return null;
}
}
+
+ @Override
+ @Nullable protected String serializeMessageForLogging(@Nullable final TokenResponse response) {
+ if (response == null) {
+ return null;
+ }
+ if (response.indicatesSuccess()) {
+ final AccessTokenResponse successResponse = response.toSuccessResponse();
+ return MoreObjects.toStringHelper(successResponse).omitNullValues()
+ .add("customParameters", successResponse.getCustomParameters())
+ .add("tokens", successResponse.getTokens())
+ .toString();
+ } else {
+ final TokenErrorResponse errorResponse = response.toErrorResponse();
+ return MoreObjects.toStringHelper(errorResponse).omitNullValues()
+ .add("errorObject", getProtocolMessageForErrorObject(errorResponse.getErrorObject()))
+ .toString();
+ }
+ }
}
diff --git a/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/decoding/impl/UserInfoResponseDecoder.java b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/decoding/impl/UserInfoResponseDecoder.java
index 773e4d84..6a6c096e 100644
--- a/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/decoding/impl/UserInfoResponseDecoder.java
+++ b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/decoding/impl/UserInfoResponseDecoder.java
@@ -27,6 +27,7 @@ import org.apache.hc.core5.http.HttpStatus;
import org.slf4j.Logger;
import com.fasterxml.jackson.core.type.TypeReference;
+import com.google.common.base.MoreObjects;
import com.nimbusds.common.contenttype.ContentType;
import com.nimbusds.jose.util.IOUtils;
import com.nimbusds.jwt.JWT;
@@ -55,11 +56,16 @@ public class UserInfoResponseDecoder extends AbstractJSONResponseDecoderFunction
@Nonnull public static final String USERINFO_ERROR_RESPONSE_HEADER = "WWW-Authenticate";
/** Class logger. */
- @Nonnull private final Logger log = LoggerFactory.getLogger(UserInfoResponseDecoder.class);
+ @Nonnull private final Logger log = LoggerFactory.getLogger(UserInfoResponseDecoder.class);
+
+ /** Constructor.*/
+ public UserInfoResponseDecoder() {
+ setProtocolMessageLoggerSubCategory("OAUTH2");
+ }
// Checkstyle: CyclomaticComplexity|ReturnCount|MethodLength OFF
@Override
- public UserInfoResponse handleResponse(@Nullable final ClassicHttpResponse httpResponse) {
+ public UserInfoResponse doHandleResponse(@Nullable final ClassicHttpResponse httpResponse) {
if (httpResponse == null) {
log.error("HttpResponse was null, can not process response");
@@ -132,4 +138,27 @@ public class UserInfoResponseDecoder extends AbstractJSONResponseDecoderFunction
}
// Checkstyle: CyclomaticComplexity|ReturnCount|MethodLength ON
+
+ /** {@inheritDoc} */
+ @Override
+ protected String serializeMessageForLogging(final UserInfoResponse response) {
+ if (response == null) {
+ return null;
+ }
+ if (response.indicatesSuccess()) {
+ final UserInfoSuccessResponse successResponse = response.toSuccessResponse();
+ return MoreObjects.toStringHelper(successResponse).omitNullValues()
+ .add("entityContentType", successResponse.getEntityContentType())
+ .add("userInfo", successResponse.getUserInfo() != null ?
+ successResponse.getUserInfo().toJSONString() : null)
+ .add("userInfoJWT", successResponse.getUserInfoJWT() == null ? null :
+ successResponse.getUserInfoJWT().serialize())
+ .toString();
+ } else {
+ final UserInfoErrorResponse errorResponse = response.toErrorResponse();
+ return MoreObjects.toStringHelper(errorResponse).omitNullValues()
+ .add("errorObject", errorResponse.getErrorObject())
+ .toString();
+ }
+ }
}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list