[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