[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