[java-plugin-shibd-oidc] branch main updated: WIP: Complete token exchange via mock client. Hook up token validation
Codeberg
noreply at shibboleth.net
Fri Nov 14 17:09:12 UTC 2025
This is an automated email from the git hooks/post-receive script.
codeberg pushed a commit to branch main
in repository java-plugin-shibd-oidc.
View the commit online:
https://codeberg.org/Shibboleth/java-plugin-shibd-oidc/commit/78d40fac366c5053941a611cdbd4c7789beced53
The following commit(s) were added to refs/heads/main by this push:
new 78d40fa WIP: Complete token exchange via mock client. Hook up token validation
78d40fa is described below
commit 78d40fac366c5053941a611cdbd4c7789beced53
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Fri Nov 14 17:09:03 2025 +0000
WIP: Complete token exchange via mock client. Hook up token validation
- Token exchange is working
- Decrypting and signature validation is working (but switched off
signature for now as I need to sign the dummy token)
- Hooked up a number of validators. They all need to be checked.
---
.../AbstractAuthenticatableOIDCContext.java | 49 -----
.../oidc/context/AccessTokenResponseContext.java | 67 ------
.../sp/oidc/context/EndUserClaimsContext.java | 85 --------
.../sp/oidc/context/OAuth2ClientContext.java | 91 --------
.../sp/oidc/context/OIDCAuthnContext.java | 94 --------
.../RequiresSignatureVerificationPredicate.java | 91 ++++++++
.../ResourceStateFromJSONStateLookupFunction.java | 2 +-
.../idp/flows/sp/consumer/oidc/oidc-beans.xml | 236 ++++++++++++++++++++-
.../idp/flows/sp/consumer/oidc/oidc-flow.xml | 10 +-
.../shibboleth/idp/flows/sp/oidc-common-beans.xml | 42 +++-
.../sp/oidc/flows/OIDCTokenConsumerFlowTest.java | 12 +-
.../oidc/profile/impl/AbstractHttpOAuthAction.java | 2 +-
.../profile/impl/ExchangeCodeForAccessToken.java | 2 +-
.../impl/InitializeAuthorizationRequest.java | 2 +-
.../impl/InitializeOAuth2ClientContext.java | 2 +-
.../impl/ValidateOAuthAccessTokenResponse.java | 132 ++++++++++++
.../sp/oidc/profile/impl/ValidateTokenClaims.java | 172 +++++++++++++++
17 files changed, 689 insertions(+), 402 deletions(-)
diff --git a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/context/AbstractAuthenticatableOIDCContext.java b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/context/AbstractAuthenticatableOIDCContext.java
deleted file mode 100644
index 8d9801e..0000000
--- a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/context/AbstractAuthenticatableOIDCContext.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * 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.sp.oidc.context;
-
-import org.opensaml.messaging.context.BaseContext;
-
-/**
- * An abstract base class for subcontexts that carry information which may be authenticated. For example,
- * tokens received over TLS where server TLS credential validation was performed and successful.
- */
-public class AbstractAuthenticatableOIDCContext extends BaseContext {
-
- /** Flag indicating whether the information contained in this context has been authenticated. */
- private boolean authenticated;
-
- /**
- * Gets the flag indicating whether the information contained in this context has been authenticated.
- *
- * @return Returns the authenticated flag.
- */
- public boolean isAuthenticated() {
- return authenticated;
- }
-
- /**
- * Sets the flag indicating whether the information contained in this context has been authenticated.
- *
- * @param flag The flag to set.
- *
- * @return this
- */
- public AbstractAuthenticatableOIDCContext setAuthenticated(final boolean flag) {
- authenticated = flag;
- return this;
- }
-
-}
diff --git a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/context/AccessTokenResponseContext.java b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/context/AccessTokenResponseContext.java
deleted file mode 100644
index 8eeef43..0000000
--- a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/context/AccessTokenResponseContext.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * 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.sp.oidc.context;
-
-import java.time.Instant;
-
-import javax.annotation.Nullable;
-
-import com.nimbusds.openid.connect.sdk.OIDCTokenResponse;
-
-/**
- * A context to hold an OIDC token request response. If authenticated, the Token was received over a TLS protected
- * channel where TLS credential validation was performed and successful.
- */
-public class AccessTokenResponseContext extends AbstractAuthenticatableOIDCContext {
-
- /** The OAuth 2.0 and OIDC token response.*/
- @Nullable private OIDCTokenResponse tokenResponse;
-
- /** The time the token response was set onto this context.*/
- @Nullable private Instant tokenResponseCreatedAt;
-
-
- /**
- * Set the OAuth 2.0 and OIDC Token Response.
- *
- * @param token the token response
- *
- * @return this
- */
- public AccessTokenResponseContext setTokenResponse(@Nullable final OIDCTokenResponse token) {
- tokenResponse = token;
- tokenResponseCreatedAt = Instant.now();
- return this;
- }
-
- /**
- * Get the OAuth 2.0 and OIDC Token Response.
- *
- * @return the token response
- */
- @Nullable public OIDCTokenResponse getTokenResponse() {
- return tokenResponse;
- }
-
- /**
- * Get the time the token response was set onto this context.
- *
- * @return the time the token response was set onto this context
- */
- @Nullable public Instant getTokenResponseCreatedAt() {
- return tokenResponseCreatedAt;
- }
-
-}
diff --git a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/context/EndUserClaimsContext.java b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/context/EndUserClaimsContext.java
deleted file mode 100644
index 008b504..0000000
--- a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/context/EndUserClaimsContext.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * 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.sp.oidc.context;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-import org.opensaml.messaging.context.BaseContext;
-
-import com.nimbusds.jwt.JWTClaimsSet;
-import com.nimbusds.openid.connect.sdk.claims.ClaimsSet;
-
-import net.shibboleth.shared.logic.Constraint;
-
-/** A context to hold the final set of claims associated with an authenticated end-user.*/
-public class EndUserClaimsContext extends BaseContext {
-
- /**
- * The claims associated with the authenticated end-user.
- * Often an aggregate of id_token and UserInfo claims
- */
- @Nullable private ClaimsSet endUserClaims;
-
- /** The set of unprocessed id_token claims as returned from an id_token source.*/
- @Nullable private JWTClaimsSet unprocessedIdTokenClaims;
-
- /**
- * Get the claims about the authenticated end-user.
- *
- * @return the claims.
- */
- @Nullable public ClaimsSet getEndUserClaims() {
- return endUserClaims;
- }
-
- /**
- * Set the claims about the authenticated end-user.
- *
- * @param claims the claims.
- * @return this
- */
- public EndUserClaimsContext setEndUserClaims(@Nonnull final ClaimsSet claims) {
- endUserClaims = Constraint.isNotNull(claims, "Claims can not be null");
- return this;
- }
-
- /**
- * Set the id_token claims about the authenticated end-user as returned from the id_token
- * endpoint e.g. from a successful Token Response.
- *
- * <p>In contrast, the endUserClaims may contain both an aggregation of claims obtained from other
- * sources e.g. the UserInfo endpoint, and a subset of the id_token claims e.g. only 'identity' claims
- * and not 'validation' claims.</p>
- *
- * @param claims the id_token claims
- *
- * @return this
- */
- public EndUserClaimsContext setUnprocessedIdTokenClaims(@Nonnull final JWTClaimsSet claims) {
- unprocessedIdTokenClaims = Constraint.isNotNull(claims,"ID Token claims can not be null");
- return this;
- }
-
- /**
- * Get the unproccessed id_token claims.
- *
- * @return the unprocessed id_token claims.
- */
- @Nullable public JWTClaimsSet getUnprocessedIdTokenClaims() {
- return unprocessedIdTokenClaims;
- }
-
-}
diff --git a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/context/OAuth2ClientContext.java b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/context/OAuth2ClientContext.java
deleted file mode 100644
index 41abd79..0000000
--- a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/context/OAuth2ClientContext.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * 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.sp.oidc.context;
-
-import java.net.URI;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-import org.opensaml.messaging.context.BaseContext;
-
-import net.shibboleth.oidc.profile.messaging.context.OIDCPeerEntityContext;
-import net.shibboleth.shared.annotation.constraint.NotEmpty;
-import net.shibboleth.shared.logic.Constraint;
-
-/**
- * A context to store information pertaining to the OAuth2 client (Relying Party) to use in communication
- * with a OpenID Provider.
- *
- * <p>Typically a subcontext under {@link OIDCPeerEntityContext}, as it relates to the client
- * associated pairwise with the upstream OP peer.</p>
- *
- * @parent {@link OIDCPeerEntityContext}
- * @added During an OAuth 2.0 authentication request attempt
- */
-public class OAuth2ClientContext extends BaseContext {
-
- /** The client_id.*/
- @Nullable private String clientId;
-
-
- /** An redirect URI that should take preference over any automatically computed.*/
- @Nullable private URI redirectUriOverride;
-
-
- /**
- * Set the redirect_uri to use in place of any other.
- *
- * @param override the redirect_uri
- *
- * @return this
- */
- public OAuth2ClientContext setRedirectUriOverride(@Nullable final URI override) {
- redirectUriOverride = override;
- return this;
- }
-
- /**
- * Get the redirect_uri which should be used in place of any other.
- *
- * @return the redirect_uri
- */
- public URI getRedirectUriOverride() {
- return redirectUriOverride;
- }
-
- /**
- * Set the client_id.
- *
- * @param id the client_id
- *
- * @return this
- */
- public OAuth2ClientContext setClientId(@Nonnull @NotEmpty final String id) {
- clientId = Constraint.isNotEmpty(id, "ClientID can not be null or empty");
- return this;
- }
-
- /**
- * Get the client_id.
- *
- * @return the client_id
- */
- public String getClientId() {
- return clientId;
- }
-
-
-}
diff --git a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/context/OIDCAuthnContext.java b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/context/OIDCAuthnContext.java
deleted file mode 100644
index 2a37403..0000000
--- a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/context/OIDCAuthnContext.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * 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.sp.oidc.context;
-
-import java.util.function.Function;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-import org.opensaml.messaging.context.BaseContext;
-import org.opensaml.messaging.decoder.MessageDecoder;
-import org.opensaml.messaging.handler.MessageHandler;
-import org.opensaml.profile.action.ProfileAction;
-
-import net.shibboleth.shared.logic.Constraint;
-
-/**
- * Manages state during proxied OIDC authentication via a Spring Controller.
- */
-public class OIDCAuthnContext extends BaseContext {
-
- /** Outbound message handler to run prior to encoding. */
- @Nullable private MessageHandler outboundMessageHandler;
-
- /** Profile action to execute to produce outbound message response. */
- @Nonnull private final ProfileAction encodeMessageAction;
-
- /** The function to use to obtain a decoder. */
- @Nonnull private final Function<String,MessageDecoder> decoderFactory;
-
- /**
- * Constructor.
- *
- * @param action message-encoding profile action
- * @param factory the message descoder factory
- */
- public OIDCAuthnContext(@Nonnull final ProfileAction action,
- @Nonnull final Function<String,MessageDecoder> factory) {
- encodeMessageAction = Constraint.isNotNull(action, "Profile action cannot be null");
- decoderFactory = Constraint.isNotNull(factory, "MessageDecoder factory cannot be null");
- }
-
- /**
- * Get the message-encoding profile action.
- *
- * @return profile action
- */
- @Nonnull public ProfileAction getEncodeMessageAction() {
- return encodeMessageAction;
- }
-
- /**
- * Get the outbound {@link MessageHandler} to run prior to encoding.
- *
- * @return the outbound {@link MessageHandler}
- */
- @Nullable public MessageHandler getOutboundMessageHandler() {
- return outboundMessageHandler;
- }
-
- /**
- * Set the outbound {@link MessageHandler} to run prior to encoding.
- *
- * @param handler outbound {@link MessageHandler} to set
- *
- * @return this context
- */
- @Nonnull public OIDCAuthnContext setOutboundMessageHandler(@Nullable final MessageHandler handler) {
- outboundMessageHandler = handler;
- return this;
- }
-
- /**
- * Get the factory function to obtain message decoders.
- *
- * @return factory function
- */
- @Nonnull public Function<String,MessageDecoder> getMessageDecoderFactory() {
- return decoderFactory;
- }
-
-}
diff --git a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/messaging/context/logic/RequiresSignatureVerificationPredicate.java b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/messaging/context/logic/RequiresSignatureVerificationPredicate.java
new file mode 100644
index 0000000..b3eeaa1
--- /dev/null
+++ b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/messaging/context/logic/RequiresSignatureVerificationPredicate.java
@@ -0,0 +1,91 @@
+/*
+ * 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.sp.oidc.messaging.context.logic;
+
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.profile.context.navigate.ParentProfileRequestContextLookup;
+import org.slf4j.Logger;
+
+import net.shibboleth.oidc.profile.config.OIDCAuthenticationRelyingPartyProfileConfiguration;
+import net.shibboleth.oidc.profile.context.AbstractAuthenticatableOIDCContext;
+import net.shibboleth.profile.context.RelyingPartyContext;
+import net.shibboleth.profile.context.logic.messaging.AbstractRelyingPartyPredicate;
+import net.shibboleth.shared.annotation.ParameterName;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * Is successful TLS credential verification enough to validate the JWT in question or should JWT signature validation
+ * be applied? Defaults to true — signature verification is required.
+ */
+public class RequiresSignatureVerificationPredicate extends AbstractRelyingPartyPredicate {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(RequiresSignatureVerificationPredicate.class);
+
+ /** Strategy used to lookup the {@link AbstractAuthenticatableOIDCContext} to test. */
+ @Nonnull
+ private final Function<MessageContext, AbstractAuthenticatableOIDCContext> authenticatableOIDCContextLookupStrategy;
+
+ /**
+ *
+ * Constructor.
+ *
+ * @param strategy the strategy used to locate the {@link AbstractAuthenticatableOIDCContext} to test
+ */
+ public RequiresSignatureVerificationPredicate(
+ @Nonnull @ParameterName(name="authenticatableOIDCContextLookupStrategy")
+ final Function<MessageContext, AbstractAuthenticatableOIDCContext> strategy) {
+ super();
+ authenticatableOIDCContextLookupStrategy = Constraint.isNotNull(strategy,
+ "authenticatableOIDCContextLookupStrategy can not be null");
+ }
+
+ @Override
+ public boolean test(@Nullable final MessageContext msgContext) {
+ final ParentProfileRequestContextLookup<MessageContext> lookup = new ParentProfileRequestContextLookup<>();
+ final ProfileRequestContext prc = lookup.apply(msgContext);
+ if (prc == null) {
+ log.warn("Profile request context not found, signature verification will be requred");
+ return true;
+ }
+ boolean tlsServerValidationOnly = false;
+ final RelyingPartyContext rpc = getRelyingPartyContextLookupStrategy().apply(msgContext);
+
+ if (rpc != null &&
+ rpc.getProfileConfig() instanceof final OIDCAuthenticationRelyingPartyProfileConfiguration rpConfig) {
+ tlsServerValidationOnly = rpConfig.isTlsServerValidationSufficient(prc);
+ }
+
+ final AbstractAuthenticatableOIDCContext authContext =
+ authenticatableOIDCContextLookupStrategy.apply(msgContext);
+
+ if (tlsServerValidationOnly && authContext.isAuthenticated()) {
+ // No further validation required.
+ log.debug("TLS server validation was successful and sufficient, no further signature processing required");
+ return false;
+ }
+
+ return true;
+
+ }
+
+}
diff --git a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/messaging/navigate/ResourceStateFromJSONStateLookupFunction.java b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/messaging/navigate/ResourceStateFromJSONStateLookupFunction.java
index a90a128..f32b992 100644
--- a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/messaging/navigate/ResourceStateFromJSONStateLookupFunction.java
+++ b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/messaging/navigate/ResourceStateFromJSONStateLookupFunction.java
@@ -34,7 +34,7 @@ public class ResourceStateFromJSONStateLookupFunction implements Function<OAuthS
if (stateContext == null) {
return null;
}
- JSONObject stateJson = stateContext.getStateJson();
+ final JSONObject stateJson = stateContext.getStateJson();
if (stateJson == null) {
return null;
}
diff --git a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/oidc/oidc-beans.xml b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/oidc/oidc-beans.xml
index 28a6208..d6bfeae 100644
--- a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/oidc/oidc-beans.xml
+++ b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/oidc/oidc-beans.xml
@@ -144,7 +144,7 @@
<bean id="InitializeOAuth2ClientAuthenticationMethodHandler" scope="prototype"
class="net.shibboleth.sp.oidc.profile.impl.InitializeOAuth2ClientAuthenticationMethodHandler"
p:securityParametersContextLookupStrategy-ref="shibboleth.ChildLookupOrCreate.SecurityParametersFromOAuth2ClientAuthenticationContext"
- p:jwtBearerExpiryOffset="%{idp.authn.oidc.rp.client.authenticationMethod.jwt.expiryOffset:PT30S}"/>
+ p:jwtBearerExpiryOffset="%{sp.oidc.authenticationMethod.jwt.expiryOffset:PT30S}"/>
</list>
</property>
</bean>
@@ -170,5 +170,239 @@
p:providerMetadataLookupStrategy-ref="shibboleth.ChildLookup.OIDCProviderMetadataContextFromInboundMessageContext"
p:oAuth2ClientAuthenticationContextLookupStrategy-ref="shibboleth.ChildLookupOrCreate.OAuth2ClientAuthenticationContextFromInboundMessageContext"/>
+ <bean id="ValidateOAuthAccessTokenResponse" scope="prototype"
+ class="net.shibboleth.sp.oidc.profile.impl.ValidateOAuthAccessTokenResponse"/>
+
+ <bean id="PopulateIDTokenDecryptionParameters"
+ class="net.shibboleth.oidc.profile.impl.PopulateJWTDecryptionParameters" scope="prototype"
+ p:configurationLookupStrategy-ref="IDTokenDecryptionConfigurationLookup"
+ p:decryptionParametersResolver-ref="JWTDecryptionParametersResolver" />
+
+ <bean id="JWTDecryptionParametersResolver" scope="prototype"
+ class="net.shibboleth.oidc.security.jose.impl.DefaultDecryptionParametersResolver" />
+
+ <bean id="IDTokenDecryptionConfigurationLookup" lazy-init="true" scope="prototype"
+ class="net.shibboleth.oidc.profile.config.navigate.JWTDecryptionConfigurationLookupFunction"/>
+
+ <bean id="DecryptIDTokenJWE" class="net.shibboleth.oidc.security.impl.DecryptJWE" scope="prototype">
+ <property name="jwtTokenLookupStrategy">
+ <bean class="net.shibboleth.oidc.profile.context.navigate.EncryptedIDTokenLookupStrategy"/>
+ </property>
+ <property name="jwtUpdateStrategy">
+ <bean class="net.shibboleth.oidc.profile.context.navigate.IDTokenInAccessTokenUpdateStrategy" />
+ </property>
+ </bean>
+
+ <!-- ID TOKEN Signature Validation -->
+ <bean id="IDTokenSignatureValidation" parent="WebFlowInboundMessageHandlerAdaptor" scope="prototype">
+ <constructor-arg>
+ <bean class="org.opensaml.messaging.handler.impl.BasicMessageHandlerChain">
+ <property name="activationCondition">
+ <bean class="net.shibboleth.sp.oidc.messaging.context.logic.RequiresSignatureVerificationPredicate"
+ scope="prototype"
+ c:authenticatableOIDCContextLookupStrategy-ref="shibboleth.ChildLookup.AccessTokenResponseContext"/>
+ </property>
+ <property name="handlers">
+ <list>
+
+ <bean scope="prototype" class="net.shibboleth.oidc.profile.impl.PopulateJWTSignatureValidationParametersHandler">
+ <property name="signatureValidationParametersResolver">
+ <bean class="net.shibboleth.oidc.security.jose.impl.BasicSignatureValidationParametersResolver" />
+ </property>
+ <property name="configurationLookupStrategy">
+ <bean class="net.shibboleth.oidc.profile.config.navigate.MessageContextLookupFunctionAdaptor">
+ <constructor-arg>
+ <bean class="net.shibboleth.oidc.profile.config.navigate.JWTSignatureValidationConfigurationLookupFunction"/>
+ </constructor-arg>
+ </bean>
+ </property>
+ </bean>
+
+ <bean class="net.shibboleth.oidc.security.impl.JWTMessageSignatureSecurityHandler"
+ scope="prototype">
+ <property name="jwtTokenLookupStrategy">
+ <bean
+ class="net.shibboleth.profile.context.navigate.SpringExpressionContextLookupFunction"
+ c:_0="#{ T(org.opensaml.messaging.context.MessageContext) }"
+ c:outputType="#{T(com.nimbusds.jwt.SignedJWT)}"
+ c:expression="#input.getSubcontext(T(net.shibboleth.oidc.profile.context.AccessTokenResponseContext)).getTokenResponse().getOIDCTokens().getIDToken()" />
+ </property>
+ <property name="providerMetadataLookupStrategy">
+ <ref bean="shibboleth.ChildLookup.OIDCProviderMetadataFromPeerEntityContext" />
+ </property>
+ </bean>
+
+ </list>
+ </property>
+ </bean>
+ </constructor-arg>
+ <property name="errorEvent">
+ <util:constant static-field="org.opensaml.profile.action.EventIds.INVALID_MESSAGE" />
+ </property>
+ </bean>
+
+ <!-- ID Token Validation -->
+ <bean id="ValidateIDTokenClaims" scope="prototype"
+ class="net.shibboleth.sp.oidc.profile.impl.ValidateTokenClaims"
+ p:cleanupHook="#{getObject('%{sp.oidc.idtoken.jwt.claims.CleanUpHook:}')}"
+ p:claimsValidator="#{getObject('%{sp.oidc.idtoken.IDTokenClaimsValidator:}')
+ ?: getObject('DefaultIDTokenClaimsValidator')}"
+ p:jwtLookupStrategy="#{getObject('%{sp.oidc.idtoken.IDTokenLookupStrategy:}')
+ ?: getObject('DefaultIDTokenLookupStrategy')}" />
+
+ <bean id="DefaultIDTokenLookupStrategy" class="net.shibboleth.oidc.security.jwt.claims.impl.IDTokenFromResponseContextLookupStrategy"/>
+
+ <bean id="DefaultIDTokenClaimsValidator"
+ class="net.shibboleth.oidc.security.jwt.claims.impl.ChainingJWTClaimsValidator"
+ p:claimValidators-ref="IDTokenClaimsValidators" />
+
+ <util:list id="IDTokenClaimsValidators" value-type="net.shibboleth.oidc.jwt.claims.ClaimsValidator">
+ <ref bean="IDTokenRequiredClaimsValidator" />
+ <ref bean="IssuerClaimsValidator" />
+ <ref bean="AudienceClaimsValidator" />
+ <ref bean="AzpClaimRequiredValidator" />
+ <!-- ref bean="AzpClaimsValidator" /> -->
+ <ref bean="ExpiryClaimsValidator" />
+ <ref bean="IssuedAtClaimsValidator" />
+ <ref bean="NotBeforeClaimsValidator" />
+<!-- <ref bean="NonceClaimValidator" /> -->
+ <ref bean="AtHashValidator"/>
+<!-- <ref bean="AuthenticationTimeClaimValidator"/> -->
+<!-- <ref bean="ACRClaimValidator"/> -->
+ </util:list>
+
+ <bean id="IDTokenRequiredClaimsValidator"
+ class="net.shibboleth.oidc.security.jwt.claims.impl.RequiredClaimsValidator"
+ p:requiredClaims-ref="IDTokenRequiredOIDCClaims" />
+
+ <util:set id="IDTokenRequiredOIDCClaims">
+ <value>iss</value>
+ <value>sub</value>
+ <value>aud</value>
+ <value>exp</value>
+ <value>iat</value>
+ </util:set>
+ <bean id="ExpiryClaimsValidator" class="net.shibboleth.oidc.security.jwt.claims.impl.ExpiryClaimsValidator"
+ p:clockSkew="#{environment.containsProperty('sp.oidc.jwt.verifier.clockSkew') ? environment.getProperty('sp.oidc.jwt.verifier.clockSkew') : '%{idp.policy.clockSkew:PT1M}'}" />
+
+ <bean id="NotBeforeClaimsValidator"
+ class="net.shibboleth.oidc.security.jwt.claims.impl.NotBeforeClaimsValidator"
+ p:clockSkew="#{environment.containsProperty('sp.oidc.jwt.verifier.clockSkew') ? environment.getProperty('sp.oidc.jwt.verifier.clockSkew') : '%{idp.policy.clockSkew:PT1M}'}" />
+
+ <bean id="IssuedAtClaimsValidator" class="net.shibboleth.oidc.security.jwt.claims.impl.IssuedAtClaimsValidator"
+ p:clockSkew="#{environment.containsProperty('sp.oidc.jwt.verifier.clockSkew') ? environment.getProperty('sp.oidc.jwt.verifier.clockSkew') : '%{idp.policy.clockSkew:PT1M}'}"
+ p:messageLifetime="%{idp.policy.messageLifetime:PT1M}"
+ p:requiredRule="false" />
+
+ <bean id="IssuerClaimsValidator" class="net.shibboleth.oidc.security.jwt.claims.impl.ExactMatchClaimsValidator"
+ p:claimName="iss" p:valueToMatchLookupStrategy-ref="IssuerIDFromOIDCProviderMetadataContextLookupFunction" />
+
+ <!-- check AZP is required if more than one audience value -->
+ <bean id="AzpClaimRequiredValidator"
+ class="net.shibboleth.oidc.security.jwt.claims.impl.RequiredClaimsValidator">
+ <property name="activationCondition">
+ <bean id="MultipleValuesExist"
+ class="net.shibboleth.oidc.security.jwt.claims.impl.NumberOfClaimValuesActivationCondition"
+ c:claimToCheck="aud" c:numberOfValuesPredicate-ref="ManyValuesPredicate" />
+ </property>
+ <property name="requiredClaims">
+ <list>
+ <value>azp</value>
+ </list>
+ </property>
+ </bean>
+
+ <bean id="ManyValuesPredicate"
+ class="net.shibboleth.oidc.security.jwt.claims.impl.ManyValuesIntegerComparisonPredicate" />
+
+<!-- <bean id="AzpClaimsValidator" class="net.shibboleth.oidc.security.jwt.claims.impl.ExactMatchClaimsValidator"
+ p:claimName="azp" p:valueToMatchLookupStrategy-ref="ClientIDFromOAuth2ClientContextFunction">
+ <property name="activationCondition">
+ <bean id="AzpClaimExistsCondition"
+ class="net.shibboleth.oidc.security.jwt.claims.impl.ClaimExistsActivationCondition" c:claimToCheck="azp" />
+ </property>
+ </bean> -->
+
+ <bean id="IssuerIDFromOIDCProviderMetadataContextLookupFunction" scope="prototype"
+ class="net.shibboleth.oidc.profile.logic.IssuerIDFromOIDCProviderMetadataContextLookupFunction"
+ p:oIDCMetadataContextLookupStrategy-ref="OIDCProviderMetadataContextFromOutboundPeerLookupStrategy" />
+
+ <bean id="OIDCProviderMetadataContextFromOutboundPeerLookupStrategy"
+ class="net.shibboleth.oidc.profile.context.navigate.OIDCProviderMetadataFromOuboundPeerLookupStrategy" />
+
+ <bean id="AudienceClaimsValidator" class="net.shibboleth.oidc.security.jwt.claims.impl.AudienceClaimsValidator"
+ p:audienceLookupStrategy-ref="ClientIDFromOAuth2ClientContextFunction"
+ p:extraAudienceValidation="true">
+ <property name="additionalAudiencesLookupStrategy">
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.ExtraAudiencesLookupStrategy"
+ c:relyingPartyContextLookupStrategy-ref="shibboleth.ChildLookup.RelyingParty"/>
+ </property>
+ </bean>
+
+ <bean id="ClientIDFromOAuth2ClientContextFunction"
+ class="net.shibboleth.oidc.profile.context.navigate.ClientIDFromOAuth2ClientContextFunction"
+ c:oauth2ClientContextLookupStrategy-ref="shibboleth.ChildLookup.OAuth2ClientContextFromInbound" />
+
+<!-- <bean id="NonceClaimValidator" class="net.shibboleth.oidc.security.jwt.claims.impl.ExactMatchClaimsValidator"
+ p:claimName="nonce"
+ p:valueToMatchLookupStrategy="#{getObject('sp.oidc.jwt.NonceLookupStrategy') ?:
+ getObject('DefaultNonceLookupStrategy')}"
+ p:activationCondition="#{getObject('sp.oidc.jwt.NonceActivationCondition') ?:
+ getObject('DefaultNonceActivationCondition')}" /> -->
+
+<!-- <bean id="DefaultNonceActivationCondition"
+ class="net.shibboleth.idp.plugin.authn.oidc.rp.security.impl.NonceValidationActivationCondition" /> -->
+
+<!-- <bean id="DefaultNonceLookupStrategy"
+ class="net.shibboleth.idp.plugin.authn.oidc.rp.security.impl.AuthenticationRequestNonceClaimLookupStrategy" /> -->
+
+ <bean id="OIDCMetadataContextChildLookup" class="org.opensaml.messaging.context.navigate.ChildContextLookup"
+ c:type="#{ T(net.shibboleth.oidc.metadata.context.OIDCMetadataContext) }" />
+
+ <bean id="OIDCPeerEntityContextChildLookup" class="org.opensaml.messaging.context.navigate.ChildContextLookup"
+ c:type="#{ T(net.shibboleth.oidc.profile.messaging.context.OIDCPeerEntityContext) }" />
+
+ <bean id="AtHashValidator" class="net.shibboleth.oidc.security.jwt.claims.impl.AccessTokenHashValidator"
+ p:allowMissing="%{sp.oidc.tokenresponse.allowMissingAtHash:true}"
+ p:accessTokenLookupStrategy="#{getObject('%{sp.oidc.jwt.AccessTokenLookupStrategy;}') ?:
+ getObject('DefaultAccessTokenLookupStrategy')}"
+ p:joseHeaderLookupStrategy="#{getObject('%{sp.oidc.jwt.IDTokenJOSEHeaderLookupStrategy:}') ?:
+ getObject('DefaultIDTokenJOSEHeaderLookupStrategy')}"/>
+
+ <bean id="DefaultAccessTokenLookupStrategy"
+ class="net.shibboleth.oidc.profile.context.navigate.AccessTokenLookupStrategy"/>
+
+ <bean id="DefaultIDTokenJOSEHeaderLookupStrategy"
+ class="net.shibboleth.oidc.profile.context.navigate.IDTokenJOSEHeaderLookupStrategy"/>
+
+<!-- <bean id="AuthenticationTimeClaimValidator"
+ class="net.shibboleth.oidc.security.jwt.claims.impl.AuthenticationTimeClaimsValidator"
+ p:authnLifetimeLookupStrategy-ref="MaxAgeLookupFunction"
+ p:authnRequestTimeLookupStrategy-ref="AuthenticationRequestTimeLookupFunction"
+ p:clockSkew="%{sp.oidc.idtoken.jwt.verifier.clockSkew:PT60S}"
+ p:activationCondition="#{getObject('sp.oidc.jwt.AuthTimeActivationCondition') ?:
+ getObject('DefaultAuthTimeActivationCondition')}"/> -->
+
+ <bean id="MaxAgeLookupFunction" scope="prototype"
+ class="net.shibboleth.oidc.security.jwt.claims.impl.MaxAgeLookupFunction"
+ c:maxAgeDefault="%{sp.oidc.idtoken.jwt.verifier.authnLifetime:PT60S}"/>
+
+<!-- <bean id="AuthenticationRequestTimeLookupFunction"
+ class="net.shibboleth.idp.plugin.authn.oidc.rp.context.navigate.AuthenticationRequestTimeLookupFunction"
+ p:clockSkew="%{sp.oidc.idtoken.jwt.verifier.authnRequestClockSkew:PT0S}"/> -->
+
+<!-- <bean id="DefaultAuthTimeActivationCondition"
+ class="net.shibboleth.oidc.security.jwt.claims.impl.AuthTimeRequestedActivationCondition"
+ c:authenticationRequestLookupStrategy-ref="shibboleth.ChildLookup.MessageLookup.Outbound.OIDCAuthenticationRequest"/> -->
+
+<!-- <bean id="ACRClaimValidator" class="net.shibboleth.oidc.security.jwt.claims.impl.ACRClaimsValidator"
+ p:requestedEssentialAcrsClaimLookupStrategy="#{getObject('sp.oidc.jwt.RequestedEssentialAcrsClaimLookupStrategy') ?:
+ getObject('DefaultRequestedEssentialAcrsClaimLookupStrategy')}"/>
+
+ <bean id="DefaultRequestedEssentialAcrsClaimLookupStrategy"
+ class="net.shibboleth.oidc.security.jwt.claims.impl.RequestedEssentialACRClaimsLookupStrategy"
+ c:authenticationRequestLookupStrategy-ref="shibboleth.ChildLookup.MessageLookup.Outbound.OIDCAuthenticationRequest"/> -->
+
+ <!-- - End ID Token Claims Validation -->
</beans>
diff --git a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/oidc/oidc-flow.xml b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/oidc/oidc-flow.xml
index c83e9a2..1e054fe 100644
--- a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/oidc/oidc-flow.xml
+++ b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/oidc/oidc-flow.xml
@@ -46,13 +46,13 @@
<action-state id="AuthorizationCodeFlow">
<evaluate expression="InitializeOAuth2ClientAuthenticationContextHandler" />
<evaluate expression="ExchangeCodeForAccessToken" />
- <!-- <evaluate expression="ValidateOAuthAccessTokenResponse" />
+ <evaluate expression="ValidateOAuthAccessTokenResponse" />
<evaluate expression="PopulateIDTokenDecryptionParameters" />
- <evaluate expression="DecryptIDTokenJWE" /> -->
+ <evaluate expression="DecryptIDTokenJWE" />
<!--Validation of the JWT signature is optional if TLS server validation was performed -->
-<!-- <evaluate expression="IDTokenSignatureValidation" />
- <evaluate expression="ValidateIDTokenClaims" />
- <evaluate expression="TokenResponsePopulateAuditContext" /> -->
+ <!-- <evaluate expression="IDTokenSignatureValidation" /> -->
+ <evaluate expression="ValidateIDTokenClaims" />
+ <!-- <evaluate expression="TokenResponsePopulateAuditContext" /> -->
<evaluate expression="'proceed'" />
<transition on="proceed" to="proceed" />
</action-state>
diff --git a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/oidc-common-beans.xml b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/oidc-common-beans.xml
index af18dd9..04caae8 100644
--- a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/oidc-common-beans.xml
+++ b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/oidc-common-beans.xml
@@ -45,7 +45,22 @@
<bean id="shibboleth.ChildLookup.OAuth2ClientAuthenticationContext"
class="org.opensaml.messaging.context.navigate.ChildContextLookup"
- c:type="#{ T(net.shibboleth.oidc.authn.context.OAuth2ClientAuthenticationContext) }"/>
+ c:type="#{ T(net.shibboleth.oidc.authn.context.OAuth2ClientAuthenticationContext) }"/>
+
+ <bean id="shibboleth.ChildLookup.AccessTokenResponseContext"
+ class="org.opensaml.messaging.context.navigate.ChildContextLookup"
+ c:type="#{ T(net.shibboleth.oidc.profile.context.AccessTokenResponseContext) }" />
+
+ <bean id="shibboleth.ChildLookup.ProviderMetadataFromProviderContext"
+ class="net.shibboleth.profile.context.navigate.SpringExpressionContextLookupFunction"
+ c:_0="#{ T(net.shibboleth.oidc.metadata.context.OIDCProviderMetadataContext) }"
+ c:outputType="#{T(com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata)}"
+ c:expression="#input.getProviderInformation()" />
+
+ <bean id="shibboleth.ChildLookup.OAuth2ClientContext"
+ class="org.opensaml.messaging.context.navigate.ChildContextLookup"
+ c:type="#{ T(net.shibboleth.oidc.profile.context.OAuth2ClientContext) }" />
+
<bean id="shibboleth.ChildLookup.OIDCProviderMetadataContextFromPeerContext" parent="shibboleth.Functions.Compose">
<constructor-arg name="g">
@@ -56,6 +71,31 @@
</constructor-arg>
</bean>
+ <bean id="shibboleth.ChildLookup.OAuth2ClientContextFromInbound" parent="shibboleth.Functions.Compose">
+ <constructor-arg name="g">
+ <ref bean="shibboleth.ChildLookup.OAuth2ClientContext" />
+ </constructor-arg>
+ <constructor-arg name="f">
+ <ref bean="shibboleth.ChildLookup.OIDCPeerEntityFromInbound" />
+ </constructor-arg>
+ </bean>
+
+ <bean id="shibboleth.ChildLookup.OIDCPeerEntityFromOutbound" parent="shibboleth.Functions.Compose"
+ c:g-ref="shibboleth.ChildLookup.OIDCPeerEntityContext" c:f-ref="shibboleth.MessageContextLookup.Outbound" />
+
+ <bean id="shibboleth.ChildLookup.OIDCPeerEntityFromInbound" parent="shibboleth.Functions.Compose"
+ c:g-ref="shibboleth.ChildLookup.OIDCPeerEntityContext" c:f-ref="shibboleth.MessageContextLookup.Inbound" />
+
+ <bean id="shibboleth.ChildLookup.OIDCProviderMetadataFromPeerEntityContext"
+ parent="shibboleth.Functions.Compose">
+ <constructor-arg name="g">
+ <ref bean="shibboleth.ChildLookup.ProviderMetadataFromProviderContext" />
+ </constructor-arg>
+ <constructor-arg name="f">
+ <ref bean="shibboleth.ChildLookup.OIDCProviderMetadataContextFromPeerContext" />
+ </constructor-arg>
+ </bean>
+
<bean id="shibboleth.ChildLookupOrCreate.OAuth2ClientAuthenticationContextFromOIDCPeer" parent="shibboleth.Functions.Compose">
<constructor-arg name="g">
<ref bean="shibboleth.ChildLookup.OAuth2ClientAuthenticationContext" />
diff --git a/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCTokenConsumerFlowTest.java b/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCTokenConsumerFlowTest.java
index b019f78..899f49b 100644
--- a/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCTokenConsumerFlowTest.java
+++ b/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCTokenConsumerFlowTest.java
@@ -21,6 +21,7 @@ import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
+import java.time.Instant;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
@@ -146,17 +147,19 @@ public class OIDCTokenConsumerFlowTest extends AbstractSPFlowTest {
/**
* Construct a successful OIDC token response.
*
+ * @param expiry expiry time
+ * @param issuedAt issue time
* @return the tokens
*/
// TODO need to sign the ID token
- private OIDCTokens constructSuccessfulTokenResponse() {
+ private OIDCTokens constructSuccessfulTokenResponse(@Nonnull final Instant expiry, @Nonnull final Instant issuedAt) {
final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
.subject("fake-user")
.issuer("https://op.example.org")
.audience("mock-client-id")
- .expirationTime(new Date(System.currentTimeMillis() + 3600_000)) // 1 hour
- .issueTime(new Date())
+ .expirationTime(Date.from(expiry))
+ .issueTime(Date.from(issuedAt))
.build();
final SignedJWT idToken = new SignedJWT(new JWSHeader(JWSAlgorithm.RS256), claimsSet);
@@ -176,7 +179,8 @@ public class OIDCTokenConsumerFlowTest extends AbstractSPFlowTest {
public void testSuccess() throws IOException {
// Create a mocked successful token response
- final OIDCTokenResponse accessTokenResponse = new OIDCTokenResponse(constructSuccessfulTokenResponse());
+ final OIDCTokenResponse accessTokenResponse = new OIDCTokenResponse(
+ constructSuccessfulTokenResponse(Instant.now().plusSeconds(3600), Instant.now()));
Mockito.when(httpClient.execute((ClassicHttpRequest) Mockito.any(), (HttpContext) Mockito.any(),
(HttpClientResponseHandler) Mockito.any())).thenReturn(accessTokenResponse);
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/AbstractHttpOAuthAction.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/AbstractHttpOAuthAction.java
index 8492b87..a710590 100644
--- a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/AbstractHttpOAuthAction.java
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/AbstractHttpOAuthAction.java
@@ -34,12 +34,12 @@ import com.nimbusds.oauth2.sdk.ErrorObject;
import com.nimbusds.oauth2.sdk.ErrorResponse;
import com.nimbusds.oauth2.sdk.Response;
+import net.shibboleth.oidc.profile.context.AbstractAuthenticatableOIDCContext;
import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
import net.shibboleth.shared.annotation.constraint.ThreadSafeAfterInit;
import net.shibboleth.shared.component.ComponentInitializationException;
import net.shibboleth.shared.logic.Constraint;
import net.shibboleth.shared.primitive.LoggerFactory;
-import net.shibboleth.sp.oidc.context.AbstractAuthenticatableOIDCContext;
import net.shibboleth.sp.oidc.exception.OIDCRPException;
/**
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/ExchangeCodeForAccessToken.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/ExchangeCodeForAccessToken.java
index 424731d..c372a70 100644
--- a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/ExchangeCodeForAccessToken.java
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/ExchangeCodeForAccessToken.java
@@ -31,10 +31,10 @@ import com.nimbusds.openid.connect.sdk.AuthenticationSuccessResponse;
import com.nimbusds.openid.connect.sdk.OIDCTokenResponse;
import net.shibboleth.idp.authn.AuthnEventIds;
+import net.shibboleth.oidc.profile.context.AccessTokenResponseContext;
import net.shibboleth.oidc.profile.messaging.context.OIDCPeerEntityContext;
import net.shibboleth.shared.logic.Constraint;
import net.shibboleth.shared.primitive.LoggerFactory;
-import net.shibboleth.sp.oidc.context.AccessTokenResponseContext;
import net.shibboleth.sp.oidc.exception.OIDCRPException;
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/InitializeAuthorizationRequest.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/InitializeAuthorizationRequest.java
index 669a45d..6a1b23b 100644
--- a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/InitializeAuthorizationRequest.java
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/InitializeAuthorizationRequest.java
@@ -29,12 +29,12 @@ import com.nimbusds.oauth2.sdk.id.ClientID;
import net.shibboleth.idp.profile.AbstractProfileAction;
import net.shibboleth.idp.profile.IdPEventIds;
+import net.shibboleth.oidc.profile.context.OAuth2ClientContext;
import net.shibboleth.oidc.profile.core.OIDCAuthenticationRequest;
import net.shibboleth.oidc.profile.messaging.context.OIDCPeerEntityContext;
import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
import net.shibboleth.shared.logic.Constraint;
import net.shibboleth.shared.primitive.LoggerFactory;
-import net.shibboleth.sp.oidc.context.OAuth2ClientContext;
/**
* An action that creates an {@link OIDCAuthenticationRequest} shell to populate in future steps,
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/InitializeOAuth2ClientContext.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/InitializeOAuth2ClientContext.java
index 3941ed7..31e8720 100644
--- a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/InitializeOAuth2ClientContext.java
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/InitializeOAuth2ClientContext.java
@@ -29,6 +29,7 @@ import org.slf4j.Logger;
import net.shibboleth.idp.profile.AbstractProfileAction;
import net.shibboleth.idp.profile.IdPEventIds;
import net.shibboleth.oidc.profile.config.OIDCAuthenticationRelyingPartyProfileConfiguration;
+import net.shibboleth.oidc.profile.context.OAuth2ClientContext;
import net.shibboleth.oidc.profile.messaging.context.OIDCPeerEntityContext;
import net.shibboleth.profile.context.RelyingPartyContext;
import net.shibboleth.profile.context.navigate.IssuerLookupFunction;
@@ -37,7 +38,6 @@ import net.shibboleth.shared.logic.Constraint;
import net.shibboleth.shared.primitive.LoggerFactory;
import net.shibboleth.shared.primitive.StringSupport;
import net.shibboleth.sp.oidc.config.navigate.RedirectUriLookupFunction;
-import net.shibboleth.sp.oidc.context.OAuth2ClientContext;
import net.shibboleth.sp.profile.AbstractAgentAction;
/**
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/ValidateOAuthAccessTokenResponse.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/ValidateOAuthAccessTokenResponse.java
new file mode 100644
index 0000000..3128396
--- /dev/null
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/ValidateOAuthAccessTokenResponse.java
@@ -0,0 +1,132 @@
+/*
+ * 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.sp.oidc.profile.impl;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.profile.context.navigate.InboundMessageContextLookup;
+import org.slf4j.Logger;
+
+import com.nimbusds.openid.connect.sdk.OIDCTokenResponse;
+
+import net.shibboleth.oidc.profile.context.AccessTokenResponseContext;
+import net.shibboleth.oidc.profile.core.OidcEventIds;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * Validation action that validates the OAuth Access Token Response against RFC 6749 section 5.1
+ * and OpenID Connect Core 1.0 section 3.1.3.3.
+ *
+ * <p>Validation will also be performed in the response decoders when creating the access token.</p>
+ *
+ * @event {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID}
+ * @event {@link EventIds#INVALID_PROFILE_CTX}
+ * @event {@link OidcEventIds#INVALID_ACCESS_TOKEN}
+ */
+public class ValidateOAuthAccessTokenResponse extends AbstractAuthorizationResponseAction {
+
+ /** Class logger. */
+ @Nonnull @NotEmpty private final Logger log = LoggerFactory.getLogger(ValidateOAuthAccessTokenResponse.class);
+
+ /** Strategy used to look up the {@link AccessTokenResponseContext} to validate. */
+ @Nonnull private Function<ProfileRequestContext, AccessTokenResponseContext>
+ tokenResponseContextLookupStrategy;
+
+ /** Constructor.*/
+ public ValidateOAuthAccessTokenResponse() {
+ tokenResponseContextLookupStrategy =
+ new ChildContextLookup<>(AccessTokenResponseContext.class, true).compose(
+ new InboundMessageContextLookup());
+ }
+
+ /**
+ * Set the strategy used to look up a {@link AccessTokenResponseContext}.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setTokenResponseContextLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext, AccessTokenResponseContext> strategy) {
+ checkSetterPreconditions();
+
+ tokenResponseContextLookupStrategy = Constraint.isNotNull(strategy,
+ "TokenResponseContext lookup strategy cannot be null");
+ }
+
+ // Checkstyle: CyclomaticComplexity OFF
+ /** {@inheritDoc} */
+ @Override protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+ final AccessTokenResponseContext responseCtx = tokenResponseContextLookupStrategy.apply(profileRequestContext);
+ if (responseCtx == null) {
+ log.debug("{} No TokenResponseContext returned by lookup strategy", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+ return;
+ }
+ final OIDCTokenResponse tokenResponse = responseCtx.getTokenResponse();
+ if (tokenResponse == null) {
+ log.warn("{} No Access Token response found, response invalid", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_ACCESS_TOKEN);
+ return;
+ }
+ // Look for an error response. This may never get here depending on the upflow response decoder used.
+ if (!tokenResponse.indicatesSuccess()) {
+ if (log.isWarnEnabled()) {
+ log.warn("{} Error response found instead of access token response", getLogPrefix());
+ }
+ ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_ACCESS_TOKEN);
+ return;
+ }
+
+ if (tokenResponse.getOIDCTokens().getIDToken() == null) {
+ log.warn("{} Access token response is invalid, no id_token found", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_ACCESS_TOKEN);
+ return;
+ }
+ // Otherwise check is valid success response parameters
+ if (tokenResponse.getTokens().getAccessToken() == null) {
+ log.warn("{} Access token response is invalid, no access_token found", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_ACCESS_TOKEN);
+ return;
+ }
+ if (tokenResponse.getTokens().getBearerAccessToken() == null) {
+ log.warn("{} Access token response is invalid, bearer token_type required", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_ACCESS_TOKEN);
+ return;
+ }
+ final Instant tokenResponseCreatedAt = responseCtx.getTokenResponseCreatedAt();
+ if (tokenResponse.getTokens().getAccessToken().getLifetime() != 0 &&
+ tokenResponseCreatedAt != null) {
+ final Instant now = Instant.now();
+ final Instant expiresAt = tokenResponseCreatedAt.plus(Duration.ofSeconds(
+ tokenResponse.getTokens().getAccessToken().getLifetime()));
+ if (expiresAt.isBefore(now)) {
+ log.warn("{} Access token response is invalid, token has expired", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_ACCESS_TOKEN);
+ return;
+ }
+ }
+ }
+ // Checkstyle: CyclomaticComplexity ON
+}
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/ValidateTokenClaims.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/ValidateTokenClaims.java
new file mode 100644
index 0000000..0583054
--- /dev/null
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/ValidateTokenClaims.java
@@ -0,0 +1,172 @@
+/*
+ * 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.sp.oidc.profile.impl;
+
+import java.text.ParseException;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+
+import com.nimbusds.jwt.JWT;
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.idp.authn.AuthnEventIds;
+import net.shibboleth.idp.profile.AbstractProfileAction;
+import net.shibboleth.oidc.jwt.claims.ClaimsValidator;
+import net.shibboleth.oidc.jwt.claims.JWTValidationException;
+import net.shibboleth.oidc.profile.core.OidcEventIds;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.sp.oidc.exception.OIDCRPException;
+
+/**
+ * Action that validates the claims of a JWT using the supplied
+ * {@link ClaimsValidator claims validator}. The verifier <b>must</b> be thread-safe and validate, at
+ * minimum, the claims set against the OpenID Connect core 1.0 section 3.1.3.7 specification.
+ *
+ *
+ * @pre <pre>ProfileRequestContext.getSubcontext(AuthenticationContext.class, false) != null</pre>
+ * @pre <pre>JWT.getJWTClaimsSet() != null</pre>
+ *
+ * @event {@link EventIds#PROCEED_EVENT_ID}
+ * @event {@link AuthnEventIds#INVALID_AUTHN_CTX}
+ * @event {@link OidcEventIds#INVALID_TOKEN}
+ */
+public class ValidateTokenClaims extends AbstractProfileAction {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(ValidateTokenClaims.class);
+
+ /** A cleanup hook to execute after either a successful or unsuccessful claims validation. */
+ @Nullable private Consumer<ProfileRequestContext> cleanupHook;
+
+ /** The parsed claimset. */
+ @NonnullBeforeExec private JWTClaimsSet claimsSet;
+
+ /** The JWT claims validator used to verify the claimsset.*/
+ @NonnullAfterInit private ClaimsValidator claimsValidator;
+
+ /** Strategy used to pull out a JWT to validate from the context.*/
+ @NonnullAfterInit private Function<ProfileRequestContext, JWT> jwtLookupStrategy;
+
+ /** {@inheritDoc} */
+ @Override protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+
+ if (claimsValidator == null) {
+ throw new ComponentInitializationException("JWT ClaimSet Validator cannot be null");
+ }
+ if (jwtLookupStrategy == null) {
+ throw new ComponentInitializationException("JWT lookup strategy cannot be null");
+ }
+ }
+
+ /**
+ * Set the lookup strategy that locates the JWT to validate from the context.
+ *
+ * @param strategy the strategy
+ */
+ public void setJwtLookupStrategy(@Nonnull final Function<ProfileRequestContext, JWT> strategy) {
+ checkSetterPreconditions();
+
+ jwtLookupStrategy = Constraint.isNotNull(strategy," JWT lookup strategy can not be null");
+ }
+
+ /**
+ * Set the cleanup hook to execute after either a successful or unsuccessful claims validation.
+ *
+ * @param hook cleanup hook
+ *
+ */
+ public void setCleanupHook(@Nullable final Consumer<ProfileRequestContext> hook) {
+ checkSetterPreconditions();
+
+ cleanupHook = hook;
+ }
+
+ /**
+ * Set the JWT claims verifier to use.
+ *
+ * @param validator the claims validator.
+ */
+ public void setClaimsValidator(
+ @Nonnull final ClaimsValidator validator) {
+ checkSetterPreconditions();
+
+ claimsValidator = Constraint.isNotNull(validator, "Claims validator cannot be null");
+ }
+
+ @Override
+ protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+ if (!super.doPreExecute(profileRequestContext)) {
+ return false;
+ }
+
+ final JWT token = jwtLookupStrategy.apply(profileRequestContext);
+ if (token == null) {
+ log.error("{} JWT was not located, nothing to validate", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.INVALID_AUTHN_CTX);
+ return false;
+ }
+ try {
+ //parse the claimset here, so parsing only has to happen once, and we fail fast on error (e.g. bad JSON)
+ claimsSet = token.getJWTClaimsSet();
+ if (claimsSet == null) {
+ throw new OIDCRPException("JWT ClaimsSet is null");
+ }
+ } catch (final ParseException | OIDCRPException e) {
+ log.error("{} JWT Claimset is not available", getLogPrefix(),e);
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.INVALID_AUTHN_CTX);
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+ final String subject = claimsSet.getSubject() != null ? claimsSet.getSubject() : "unknown subject";
+ log.debug("{} Validating JWT claims for subject '{}'",getLogPrefix(), subject);
+
+ try {
+ claimsValidator.validate(claimsSet, profileRequestContext);
+ if (cleanupHook != null) {
+ cleanupHook.accept(profileRequestContext);
+ }
+ } catch (final JWTValidationException e) {
+ log.error("{} JWT claims verification failed for subject '{}'", getLogPrefix(), subject, e);
+ ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_TOKEN);
+ if (cleanupHook != null) {
+ cleanupHook.accept(profileRequestContext);
+ }
+ return;
+ }
+ //fine.
+ log.debug("{} JWT claims are valid for subject '{}'",getLogPrefix(),claimsSet.getSubject());
+ }
+
+
+}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list