[java-oidc-common] branch dev/JCOMOIDC-139 updated: Add JSON support to state token. Add post decoder support

Phil Smart philip.smart at jisc.ac.uk
Tue Nov 4 16:35:06 UTC 2025


This is an automated email from the git hooks/post-receive script.

philsmart pushed a commit to branch dev/JCOMOIDC-139
in repository java-oidc-common.

View the commit online:
https://git.shibboleth.net/view/?p=java-oidc-common.git;a=commit;h=e2488e4e2566d5bfd3dc761e1f15a42fcca23afe

The following commit(s) were added to refs/heads/dev/JCOMOIDC-139 by this push:
     new e2488e4  Add JSON support to state token. Add post decoder support
e2488e4 is described below

commit e2488e4e2566d5bfd3dc761e1f15a42fcca23afe
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Tue Nov 4 16:35:03 2025 +0000

    Add JSON support to state token. Add post decoder support
    
     - Allow for post decoders that can, for example, decode state to a JSON
    Object
     - Support JSON based state tokens.
---
 .../profile/core/OAuthAuthorizationRequest.java    | 18 ++++-
 .../shibboleth/oidc/profile/core/StateToken.java   | 63 ++++++++++++++++
 .../decoding/impl/BaseHttpOIDCRequestDecoder.java  | 86 ++++++++++++++++++++++
 .../impl/HTTPPostAuthnResponseDecoder.java         |  7 +-
 .../impl/HTTPRedirectAuthnResponseDecoder.java     | 10 +--
 .../messaging/handler/impl/AddStateHandler.java    | 21 +++---
 6 files changed, 179 insertions(+), 26 deletions(-)

diff --git a/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/core/OAuthAuthorizationRequest.java b/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/core/OAuthAuthorizationRequest.java
index aeedfff..88a4b81 100644
--- a/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/core/OAuthAuthorizationRequest.java
+++ b/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/core/OAuthAuthorizationRequest.java
@@ -25,6 +25,7 @@ import com.nimbusds.oauth2.sdk.Scope;
 import com.nimbusds.oauth2.sdk.id.ClientID;
 import com.nimbusds.oauth2.sdk.id.State;
 
+import net.minidev.json.JSONObject;
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
 import net.shibboleth.shared.logic.Constraint;
 
@@ -38,7 +39,7 @@ public class OAuthAuthorizationRequest {
     @Nonnull private final ClientID clientID;
     
     /** The state. */
-    @Nullable private State state;
+    @Nullable private StateToken state;
     
     /** The redirect URI to which the response will be sent. */
     @Nullable private URI redirectURI;
@@ -153,7 +154,18 @@ public class OAuthAuthorizationRequest {
      * @return the state.
      */
     @Nullable public State getState() {
-        return state;
+        return state != null ? new State(state.getValue()) : null;
+    }
+    
+    /**
+     * Get the state as a JSON Object iff the state was encoded as a JSON object.
+     * 
+     * @return the state.
+     * 
+     * @since 3.4.0
+     */
+    @Nullable public JSONObject getStateJson() {
+        return state != null ? state.getJson() : null;
     }
 
     /**
@@ -161,7 +173,7 @@ public class OAuthAuthorizationRequest {
      * 
      * @param theState The state to set.
      */
-    public void setState(@Nullable final State theState) {
+    public void setState(@Nullable final StateToken theState) {
         state = theState;
     }
 
diff --git a/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/core/StateToken.java b/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/core/StateToken.java
new file mode 100644
index 0000000..87ee8ce
--- /dev/null
+++ b/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/core/StateToken.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.oidc.profile.core;
+
+import javax.annotation.Nullable;
+
+import net.minidev.json.JSONObject;
+
+/**
+ * State token holder class.
+ */
+public class StateToken {
+    
+    
+    /** The value of the state token in its string form ready to be added to an OAuth request. */
+    private final String value;
+    
+    /** The JSON object form of the state token if the state is encoded as a JSON object. */
+    private final JSONObject json;
+    
+    /**
+     * 
+     * Constructor.
+     *
+     * @param stateValue the value of the state token in its string, can be null
+     * @param jsonState the JSON object form of the state token, can be null.
+     */
+    public StateToken(@Nullable final String stateValue, @Nullable final JSONObject jsonState) {
+        value = stateValue;
+        json = jsonState;
+    }
+    
+    /**
+     * Get the value of the state token in its string form ready to be added to an OAuth request.
+     * 
+     * @return the state token value
+     */
+    @Nullable public String getValue() {
+        return value;
+    }
+    
+    /**
+     * Get the JSON object form of the state token.
+     * @return
+     */
+    @Nullable public JSONObject getJson() {
+        return json;
+    }
+    
+
+}
diff --git a/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/decoding/impl/BaseHttpOIDCRequestDecoder.java b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/decoding/impl/BaseHttpOIDCRequestDecoder.java
new file mode 100644
index 0000000..fb37f88
--- /dev/null
+++ b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/decoding/impl/BaseHttpOIDCRequestDecoder.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.oidc.profile.decoding.impl;
+
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.decoder.MessageDecodingException;
+import org.opensaml.messaging.decoder.servlet.AbstractHttpServletRequestMessageDecoder;
+
+import net.shibboleth.oidc.profile.decoding.OIDCMessageDecoder;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.logic.FunctionSupport;
+
+/**
+ * A base class for OpenID Connect request decoders. 
+ */
+public abstract class BaseHttpOIDCRequestDecoder extends AbstractHttpServletRequestMessageDecoder 
+                    implements OIDCMessageDecoder {
+    
+    /** A flag to indicate whether a failure in the post decode strategy should be treated as an error. */
+    private boolean postDecodeStrategyFailureIsError = false;
+    
+    /** A strategy function to run after decoding. Returns success as true and failure as false. */
+    @Nonnull private Function<MessageContext, Boolean> postDecodeStrategy;
+    
+    
+    /** Constructor.*/
+    protected BaseHttpOIDCRequestDecoder() {
+        postDecodeStrategy = FunctionSupport.constant(true);
+    }
+    
+    /**
+     * Set a flag to indicate whether a failure in the post decode strategy should be treated as an error.
+     * 
+     * @param flag the flag to set.
+     */
+    public void setPostDecodeStrategyFailureIsError(final boolean flag) {
+        checkSetterPreconditions();
+        postDecodeStrategyFailureIsError = flag;
+    }
+    
+    /**
+     * Set the post decode strategy function.
+     * 
+     * @param strategy The postDecodeStrategy to set.
+     */
+    public void setPostDecodeStrategy(@Nonnull final Function<MessageContext, Boolean> strategy) {
+        checkSetterPreconditions();
+        postDecodeStrategy = Constraint.isNotNull(strategy, "PostDecodeStrategy can not be null");
+    }
+    
+    @Override
+    protected void doDecode() throws MessageDecodingException {
+        performDecode();
+        final boolean success = postDecodeStrategy.apply(getMessageContext());
+        if (!success && postDecodeStrategyFailureIsError) {
+            throw new MessageDecodingException("Post decode strategy reported failure");
+        }
+    }
+    
+    /**
+     * Performs the decoding logic. By the time this is called, this decoder has already been initialized and checked to
+     * ensure that it has not been destroyed.
+     * 
+     * @throws MessageDecodingException thrown if there is a problem decoding the message
+     */
+    protected abstract void performDecode() throws MessageDecodingException;
+    
+    
+
+}
diff --git a/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/decoding/impl/HTTPPostAuthnResponseDecoder.java b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/decoding/impl/HTTPPostAuthnResponseDecoder.java
index c129147..c4f75a9 100644
--- a/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/decoding/impl/HTTPPostAuthnResponseDecoder.java
+++ b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/decoding/impl/HTTPPostAuthnResponseDecoder.java
@@ -22,7 +22,6 @@ import javax.annotation.Nullable;
 import org.opensaml.messaging.context.MessageContext;
 import org.opensaml.messaging.decoder.MessageDecoder;
 import org.opensaml.messaging.decoder.MessageDecodingException;
-import org.opensaml.messaging.decoder.servlet.AbstractHttpServletRequestMessageDecoder;
 import org.slf4j.Logger;
 
 import com.nimbusds.oauth2.sdk.ParseException;
@@ -31,22 +30,20 @@ import com.nimbusds.openid.connect.sdk.AuthenticationResponse;
 import com.nimbusds.openid.connect.sdk.AuthenticationResponseParser;
 
 import jakarta.servlet.http.HttpServletRequest;
-import net.shibboleth.oidc.profile.decoding.OIDCMessageDecoder;
 import net.shibboleth.shared.primitive.LoggerFactory;
 
 /** 
  * A {@link MessageDecoder message decoder} that decodes an incoming {@link AuthenticationResponse}
  * when using a form_post response_type.
  */
-public class HTTPPostAuthnResponseDecoder extends AbstractHttpServletRequestMessageDecoder
-                    implements OIDCMessageDecoder {
+public class HTTPPostAuthnResponseDecoder extends BaseHttpOIDCRequestDecoder {
     
     /** Class logger. */
     @Nonnull
     private final Logger log = LoggerFactory.getLogger(HTTPPostAuthnResponseDecoder.class);
 
     @Override
-    protected void doDecode() throws MessageDecodingException {
+    protected void performDecode() throws MessageDecodingException {
         
         log.trace("Decoding incomming 'form_post' authentication response");
         
diff --git a/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/decoding/impl/HTTPRedirectAuthnResponseDecoder.java b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/decoding/impl/HTTPRedirectAuthnResponseDecoder.java
index ec8e016..63864fb 100644
--- a/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/decoding/impl/HTTPRedirectAuthnResponseDecoder.java
+++ b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/decoding/impl/HTTPRedirectAuthnResponseDecoder.java
@@ -22,7 +22,6 @@ import javax.annotation.Nullable;
 import org.opensaml.messaging.context.MessageContext;
 import org.opensaml.messaging.decoder.MessageDecoder;
 import org.opensaml.messaging.decoder.MessageDecodingException;
-import org.opensaml.messaging.decoder.servlet.AbstractHttpServletRequestMessageDecoder;
 import org.slf4j.Logger;
 
 import com.nimbusds.oauth2.sdk.ParseException;
@@ -31,7 +30,6 @@ import com.nimbusds.openid.connect.sdk.AuthenticationResponse;
 import com.nimbusds.openid.connect.sdk.AuthenticationResponseParser;
 
 import jakarta.servlet.http.HttpServletRequest;
-import net.shibboleth.oidc.profile.decoding.OIDCMessageDecoder;
 import net.shibboleth.shared.primitive.LoggerFactory;
 
 
@@ -39,15 +37,15 @@ import net.shibboleth.shared.primitive.LoggerFactory;
  * A {@link MessageDecoder message decoder} that decodes an incoming {@link AuthenticationResponse}
  * when using a query response_mode.
  */
-public class HTTPRedirectAuthnResponseDecoder extends AbstractHttpServletRequestMessageDecoder 
-                implements OIDCMessageDecoder {
+public class HTTPRedirectAuthnResponseDecoder extends BaseHttpOIDCRequestDecoder {
     
     /** Class logger. */
     @Nonnull
     private final Logger log = LoggerFactory.getLogger(HTTPRedirectAuthnResponseDecoder.class);
 
+
     @Override
-    protected void doDecode() throws MessageDecodingException {
+    protected void performDecode() throws MessageDecodingException {
         
         log.trace("Decoding incomming 'query' authentication response");
         
@@ -68,7 +66,7 @@ public class HTTPRedirectAuthnResponseDecoder extends AbstractHttpServletRequest
         }
         setMessageContext(messageContext);        
     }
-    
+
     /** {@inheritDoc} */
     @Override
     @Nullable
diff --git a/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddStateHandler.java b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddStateHandler.java
index 193666c..2e24316 100644
--- a/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddStateHandler.java
+++ b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/messaging/handler/impl/AddStateHandler.java
@@ -20,15 +20,14 @@ import org.opensaml.messaging.context.MessageContext;
 import org.opensaml.messaging.handler.MessageHandlerException;
 import org.slf4j.Logger;
 
-import com.nimbusds.oauth2.sdk.id.State;
 import com.nimbusds.openid.connect.sdk.claims.ClaimsSet;
 
+import net.shibboleth.oidc.profile.core.StateToken;
 import net.shibboleth.shared.primitive.LoggerFactory;
 
 /** 
  * Add an OAuth 2.0 / OpenID Connect {@code state} value to the authentication request URL and the request object 
- * claims (if present). By default this is generated by concatenating the Hex value of the spring webflow execution 
- * key with a secure random 32 character nonce. 
+ * claims (if present).
  * 
  *  * <p>
  * The {@code state} parameter helps prevent cross-site request forgery
@@ -37,8 +36,7 @@ import net.shibboleth.shared.primitive.LoggerFactory;
  * </p>
  * 
  */
-//TODO This need thinking about in the case of the RP-Full.
-public class AddStateHandler extends AbstractAuthenticationRequestParameterValueMessageHandler<String> {
+public class AddStateHandler extends AbstractAuthenticationRequestParameterValueMessageHandler<StateToken> {
     
     /** The 'state' claim name.*/
     @Nonnull private static final String STATE_CLAIM = "state";
@@ -49,28 +47,27 @@ public class AddStateHandler extends AbstractAuthenticationRequestParameterValue
     
     /** Constructor.*/
     public AddStateHandler() {
-        super(String.class);
+        super(StateToken.class);
     }
 
     /** {@inheritDoc} */
     @Override
     protected void doInvoke(@Nonnull final MessageContext messageContext) throws MessageHandlerException {
         
-        final String stateString = getParameterValue(messageContext);        
-        if (stateString == null) {
+        final StateToken stateToken = getParameterValue(messageContext);        
+        if (stateToken == null) {
             throw new MessageHandlerException("Generated state was null");
         }
-        log.trace("{} Generated state '{}'", getLogPrefix(), stateString);
-        final State state = new State(stateString);
+        log.trace("{} Generated state '{}'", getLogPrefix(), stateToken.getValue());
         
         // Add to outer request
-        getAuthenticationRequest().setState(state);
+        getAuthenticationRequest().setState(stateToken);
         
         // Add to Request Object if exists
         final ClaimsSet claims = getAuthenticationRequest().getRequestObjectClaimsSet();
         if (claims != null) {            
             log.trace("{} Adding state to JWT RequestObject", getLogPrefix());
-            claims.setClaim(STATE_CLAIM, state);           
+            claims.setClaim(STATE_CLAIM, stateToken.getValue());           
         }               
     }
 

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list