[java-oidc-common] 09/20: Add JSON support to state token. Add post decoder support
Codeberg
noreply at shibboleth.net
Tue Feb 17 20:14:44 UTC 2026
This is an automated email from the git hooks/post-receive script.
codeberg pushed a commit to branch dev/JCOMOIDC-139
in repository java-oidc-common.
View the commit online:
https://codeberg.org/Shibboleth/java-oidc-common/commit/590a90a4c5bf0bad713fbf0bcc508f4bae7b36ee
commit 590a90a4c5bf0bad713fbf0bcc508f4bae7b36ee
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 aeedfffb..88a4b812 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 00000000..87ee8ce1
--- /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 00000000..fb37f88e
--- /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 c129147a..c4f75a95 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 ec8e0161..63864fbe 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 193666ce..2e24316e 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