[java-idp-oidc] 02/02: Add JWT support for UI endpoint, start fleshing out authz changes.

Scott Cantor cantor.2 at osu.edu
Mon Apr 18 17:33:04 UTC 2022


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

scantor pushed a commit to branch dev/JOIDC-7
in repository java-idp-oidc.

View the commit online:
http://git.shibboleth.net/view/?p=java-idp-oidc.git;a=commit;h=a33f97904d04b7b53d68a91b4534a9dfdc05007b

commit a33f97904d04b7b53d68a91b4534a9dfdc05007b
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Fri Apr 15 16:26:33 2022 -0400

    Add JWT support for UI endpoint, start fleshing out authz changes.
---
 .../DefaultRequestAudienceLookupFunction.java      |  69 ++++++++
 .../op/token/support/AccessTokenClaimsSet.java     |   8 +-
 .../SetAuthorizationCodeToResponseContext.java     |  21 ++-
 .../userinfo/profile/impl/ValidateAccessToken.java | 186 ++++++++++++++++++---
 .../idp/flows/oidc/authorize/authorize-beans.xml   |   8 +
 .../idp/flows/oidc/authorize/authorize-flow.xml    |   1 +
 .../idp/flows/oidc/token/token-beans.xml           |   4 -
 .../idp/service/relying-party/postconfig.xml       |  55 +++++-
 8 files changed, 310 insertions(+), 42 deletions(-)

diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestAudienceLookupFunction.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestAudienceLookupFunction.java
new file mode 100644
index 00000000..b8b72b50
--- /dev/null
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestAudienceLookupFunction.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You 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.idp.plugin.oidc.op.profile.context.navigate;
+
+import java.net.URI;
+import java.text.ParseException;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import com.nimbusds.openid.connect.sdk.AuthenticationRequest;
+
+/**
+ * A function that returns resource value of the authentication request.
+ * 
+ * @since 3.2.0
+ */
+public class DefaultRequestAudienceLookupFunction extends AbstractAuthenticationRequestLookupFunction<List<String>> {
+
+    /** Class logger. */
+    @Nonnull private Logger log = LoggerFactory.getLogger(DefaultRequestAudienceLookupFunction.class);
+
+    /** {@inheritDoc} */
+    @Nullable protected List<String> doLookup(@Nonnull final AuthenticationRequest req) {
+        try {
+            if (getRequestObject() != null && getRequestObject().getJWTClaimsSet().getClaim("resource") != null) {
+                final Object resource = getRequestObject().getJWTClaimsSet().getClaim("resource");
+                if (resource instanceof String) {
+                    return Collections.singletonList((String) resource);
+                } else if (resource instanceof List) {
+                    return (List<String>) resource;
+                } else if (resource != null) {
+                    log.error("resource claim is not of expected type");
+                    return null;
+                }
+
+            }
+        } catch (final ParseException e) {
+            log.error("Unable to parse request object");
+            return null;
+        }
+        
+        return req.getResources() == null ? null :
+            req.getResources().stream()
+                .map(URI::toString)
+                .collect(Collectors.toUnmodifiableList());
+    }
+
+}
\ No newline at end of file
diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/token/support/AccessTokenClaimsSet.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/token/support/AccessTokenClaimsSet.java
index e3ac61c4..81347dbe 100644
--- a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/token/support/AccessTokenClaimsSet.java
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/token/support/AccessTokenClaimsSet.java
@@ -63,13 +63,13 @@ public final class AccessTokenClaimsSet extends TokenClaimsSet {
         setClaimsSet(builder.buildJWTClaimsSet(VALUE_TYPE_AT));
     }
 // Checkstyle: ParameterNumber ON
-
+    
     /**
-     * Private constructor for the parser.
+     * Direct constructor.
      * 
      * @param accessTokenClaimsSet access token claims set
      */
-    protected AccessTokenClaimsSet(@Nonnull final JWTClaimsSet accessTokenClaimsSet) {
+    public AccessTokenClaimsSet(@Nonnull final JWTClaimsSet accessTokenClaimsSet) {
         super(accessTokenClaimsSet);
     }
 
@@ -204,7 +204,7 @@ public final class AccessTokenClaimsSet extends TokenClaimsSet {
         public AccessTokenClaimsSet build() {
             return new AccessTokenClaimsSet(buildJWTClaimsSet(VALUE_TYPE_AT));
         }
-        
+                
     }
 
 }
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/SetAuthorizationCodeToResponseContext.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/SetAuthorizationCodeToResponseContext.java
index a1033fac..9b784a4a 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/SetAuthorizationCodeToResponseContext.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/SetAuthorizationCodeToResponseContext.java
@@ -289,6 +289,8 @@ public class SetAuthorizationCodeToResponseContext extends AbstractOIDCAuthentic
     @Override
     protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
 
+        final OIDCAuthenticationResponseContext responseCtx = getOidcResponseContext();
+        
         final OIDCAuthenticationResponseConsentContext consentCtx =
                 consentContextLookupStrategy.apply(profileRequestContext);
         final JSONArray consented = consentCtx != null ? consentCtx.getConsentedAttributes() : null;
@@ -303,22 +305,23 @@ public class SetAuthorizationCodeToResponseContext extends AbstractOIDCAuthentic
             claimsUI = tokenClaimsCtx.getUserinfoClaims();
         }
         final Instant dateExp = Instant.now().plus(authzCodeLifetime);
-        final Scope scope = getOidcResponseContext().getScope();
+        final Scope scope = responseCtx.getScope();
         final AuthorizeCodeClaimsSet claimsSet = new AuthorizeCodeClaimsSet.Builder()
                 .setJWTID(idGenerator)
                 .setClientID(getAuthenticationRequest().getClientID())
                 .setIssuer(issuerLookupStrategy.apply(profileRequestContext))
                 .setPrincipal(subjectCtx.getPrincipalName())
-                .setSubject(getOidcResponseContext().getSubject())
+                .setSubject(responseCtx.getSubject())
                 .setIssuedAt(Instant.now())
                 .setExpiresAt(dateExp)
-                .setAuthenticationTime(getOidcResponseContext().getAuthTime())
-                .setRedirectURI(getOidcResponseContext().getRedirectURI())
+                .setAuthenticationTime(responseCtx.getAuthTime())
+                .setRedirectURI(responseCtx.getRedirectURI())
                 .setScope(scope != null ? scope : new Scope())
-                .setACR(getOidcResponseContext().getAcr())
+                .setAudience(responseCtx.getAudience())
+                .setACR(responseCtx.getAcr())
                 .setNonce(new DefaultRequestNonceLookupFunction().apply(profileRequestContext))
                 .setCodeChallenge(codeChallenge)
-                .setClaimsRequest(getOidcResponseContext().getRequestedClaims())
+                .setClaimsRequest(responseCtx.getRequestedClaims())
                 .setDlClaims(claims)
                 .setDlClaimsID(claimsID)
                 .setDlClaimsUI(claimsUI)
@@ -326,11 +329,11 @@ public class SetAuthorizationCodeToResponseContext extends AbstractOIDCAuthentic
                 .setConsentEnabled(consentEnabledPredicate.test(profileRequestContext))
                 .build();
         // We set token claims set to response context for possible access token generation.
-        getOidcResponseContext().setAuthorizationGrantClaimsSet(claimsSet);
+        responseCtx.setAuthorizationGrantClaimsSet(claimsSet);
         try {
-            getOidcResponseContext().setAuthorizationCode(claimsSet.serialize(dataSealer));
+            responseCtx.setAuthorizationCode(claimsSet.serialize(dataSealer));
             log.debug("{} Setting authz code {} as {} to response context ", getLogPrefix(), claimsSet.serialize(),
-                    getOidcResponseContext().getAuthorizationCode());
+                    responseCtx.getAuthorizationCode());
         } catch (final DataSealerException e) {
             log.error("{} Authorization Code generation failed {}", getLogPrefix(), e.getMessage());
             ActionSupport.buildEvent(profileRequestContext, EventIds.UNABLE_TO_ENCRYPT);
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/userinfo/profile/impl/ValidateAccessToken.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/userinfo/profile/impl/ValidateAccessToken.java
index 6802c05b..b0f0c20c 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/userinfo/profile/impl/ValidateAccessToken.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/userinfo/profile/impl/ValidateAccessToken.java
@@ -18,30 +18,51 @@
 package net.shibboleth.idp.plugin.oidc.op.userinfo.profile.impl;
 
 import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.function.Function;
 
 import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
 
 import org.opensaml.profile.action.ActionSupport;
 import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.security.credential.Credential;
+import org.opensaml.security.credential.CredentialResolver;
+import org.opensaml.security.credential.UsageType;
+import org.opensaml.security.criteria.UsageCriterion;
 import org.opensaml.storage.RevocationCache;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.nimbusds.jose.JOSEObjectType;
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.SignedJWT;
+import com.nimbusds.oauth2.sdk.token.AccessToken; 
+
 import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext;
-import net.shibboleth.idp.plugin.oidc.op.storage.RevocationCacheContexts;
 import net.shibboleth.idp.plugin.oidc.op.token.support.AccessTokenClaimsSet;
+import net.shibboleth.idp.profile.IdPEventIds;
+import net.shibboleth.oidc.jwt.claims.ClaimsValidator;
+import net.shibboleth.oidc.jwt.claims.JWTValidationException;
+import net.shibboleth.oidc.profile.config.navigate.IssuedClaimsValidatorLookupFunction;
 import net.shibboleth.oidc.profile.core.OidcEventIds;
+import net.shibboleth.oidc.security.impl.JWTSignatureValidationUtil;
 import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
 import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
 import net.shibboleth.utilities.java.support.component.ComponentSupport;
 import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
+import net.shibboleth.utilities.java.support.resolver.ResolverException;
 import net.shibboleth.utilities.java.support.security.DataSealer;
 import net.shibboleth.utilities.java.support.security.DataSealerException;
 
 /**
- * Action that validates access token is a valid one. Token is valid if it is successfully unwrapped, parsed as access
- * token, is not expired and authorize code it has been derived from has not been revoked. Validated token is stored to
- * response context retrievable as claims {@link OIDCAuthenticationResponseContext#getTokenClaimsSet()}.
+ * Action that validates an access token as usable for access to the OP's endpoints.
+ * 
+ * <p>The validated token is stored to the response context retrievable as claims via
+ * {@link OIDCAuthenticationResponseContext#getTokenClaimsSet()}.
  */
 public class ValidateAccessToken extends AbstractOIDCUserInfoValidationResponseAction {
 
@@ -54,6 +75,26 @@ public class ValidateAccessToken extends AbstractOIDCUserInfoValidationResponseA
     /** Message revocation cache instance to use. */
     @NonnullAfterInit private RevocationCache revocationCache;
     
+    /** Lookup strategy for claims validator. */
+    @Nonnull private Function<ProfileRequestContext,ClaimsValidator> claimsValidatorLookupStrategy;
+    
+    /** The claims validator to use. */
+    @Nullable private ClaimsValidator claimsValidator;
+    
+    /** Source of signing keys. */
+    @Nullable private CredentialResolver credentialResolver;
+    
+    /** Copy of signed JWT for non-opaque access tokens. */
+    @Nullable private SignedJWT signedJWT;
+
+    /** Our local type used for opaque tokens. */
+    @Nullable private AccessTokenClaimsSet opaqueClaimsSet;
+
+    /** Constructor. */
+    public ValidateAccessToken() {
+        claimsValidatorLookupStrategy = new IssuedClaimsValidatorLookupFunction();
+    }
+    
     /**
      * Set the data sealer instance to use.
      * 
@@ -74,6 +115,27 @@ public class ValidateAccessToken extends AbstractOIDCUserInfoValidationResponseA
         revocationCache = Constraint.isNotNull(cache, "RevocationCache cannot be null");
     }
 
+    /**
+     * Set the claims validator lookup strategy.
+     * 
+     * @param strategy lookup strategy
+     */
+    public void setClaimsValidatorLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext,ClaimsValidator> strategy) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        claimsValidatorLookupStrategy = Constraint.isNotNull(strategy, "Lookup strategy cannot be null");
+    }
+    
+    /**
+     * Set the source of signing keys to use for JWT signature verification.
+     * 
+     * @param resolver signing key resolver
+     */
+    public void setCredentialResolver(@Nullable final CredentialResolver resolver) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        credentialResolver = resolver;
+    }
+    
     /** {@inheritDoc} */
     @Override
     protected void doInitialize() throws ComponentInitializationException {
@@ -83,44 +145,122 @@ public class ValidateAccessToken extends AbstractOIDCUserInfoValidationResponseA
             throw new ComponentInitializationException("RevocationCache and DataSealer cannot be null");
         }
     }
+        
+    /** {@inheritDoc} */
+    @Override
+    protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+        if (!super.doPreExecute(profileRequestContext)) {
+            return false;
+        }
+        
+        claimsValidator = claimsValidatorLookupStrategy.apply(profileRequestContext);
+        if (claimsValidator == null) {
+            log.error("{} Unable to obtain ClaimsValidator to apply", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_PROFILE_CONFIG);
+            return false;
+        }
+        
+        return true;
+    }
+    
     
     /** {@inheritDoc} */
     @Override
     protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
 
-        final AccessTokenClaimsSet accessTokenClaimsSet;
-        try {
-            accessTokenClaimsSet =
-                    AccessTokenClaimsSet.parse(getUserInfoRequest().getAccessToken().getValue(), dataSealer);
-            log.debug("{} Access token unwrapped: {}", getLogPrefix(), accessTokenClaimsSet.serialize());
-        } catch (final DataSealerException | ParseException e) {
-            log.warn("{} Parsing access token failed: {}", getLogPrefix(), e.getMessage());
+        final AccessToken token = getUserInfoRequest().getAccessToken();
+        if (token == null) {
+            log.error("{} Token missing from request", getLogPrefix());
             ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_GRANT);
             return;
         }
         
-        if (!accessTokenClaimsSet.isTimeValid()) {
-            log.warn("{} Access token is expired or future dated", getLogPrefix());
+        JWTClaimsSet tokenClaimsSet = parseAccessToken(token);
+        if (tokenClaimsSet == null) {
+            log.warn("{} Unable to parse/decode token for validation", getLogPrefix());
             ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_GRANT);
             return;
         }
         
-        // TODO: needs to change to accomodate possibility of dual-use access tokens.
-        if (!accessTokenClaimsSet.getAudience().isEmpty()) {
-            log.warn("{} Access token was not issued for use by this OP");
+        log.debug("{} Access token unwrapped: {}", getLogPrefix(), tokenClaimsSet.toString());
+        
+        if (signedJWT != null) {
+            // Check typ header.
+            final JOSEObjectType typ = signedJWT.getHeader().getType();
+            if (typ == null || !"at+jwt".equals(typ.getType())) {
+                log.warn("{} Missing or invalid token type: {}", getLogPrefix(), typ != null ? typ.getType() : "null");
+                return;
+            }
+            
+            if (credentialResolver == null) {
+                log.error("{} No CredentialResolver available, can't verify JWT signature", getLogPrefix());
+                return;
+            }
+            
+            log.debug("{} Checking JWT signature", getLogPrefix());
+            final Collection<Credential> credList = new ArrayList<>();
+            final CriteriaSet criteriaSet = new CriteriaSet(new UsageCriterion(UsageType.SIGNING));
+            try {
+                final Iterable<Credential> creds = credentialResolver.resolve(criteriaSet);
+                if (creds != null) {
+                    creds.forEach(credList::add);
+                }
+            } catch (final ResolverException e) {
+                log.error("{} Failure resolving signing credentials, can't verify JWT signature", getLogPrefix(), e);
+                return;
+            }
+            final String errorEventId = JWTSignatureValidationUtil.validateSignatureEx(credList, signedJWT,
+                    OidcEventIds.INVALID_GRANT);
+            if (errorEventId != null) {
+                ActionSupport.buildEvent(profileRequestContext, errorEventId);
+                log.warn("{} Signature on token ID '{}' invalid", getLogPrefix(), tokenClaimsSet.getJWTID());
+                return;
+            }
+        }
+
+        log.debug("{} Validating parsed/decoded claims set: {}", getLogPrefix(), tokenClaimsSet.toString());
+        try {
+            claimsValidator.validate(tokenClaimsSet, profileRequestContext);
+        } catch (final JWTValidationException e) {
+            log.warn("{} Claims validation failed, token is invalid: {}", getLogPrefix(), e.getMessage());
             ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_GRANT);
             return;
         }
+
+        log.debug("{} Access token {} validated", getLogPrefix(), tokenClaimsSet.getJWTID());
+        if (opaqueClaimsSet == null) {
+            // Wraps the JWT claims in a custom object.
+            opaqueClaimsSet = new AccessTokenClaimsSet(tokenClaimsSet);
+        }
+        getOidcResponseContext().setAuthorizationGrantClaimsSet(opaqueClaimsSet);
+    }
+
+    /**
+     * Attempt to parse token.
+     * 
+     * @param token the token
+     * 
+     * @return parsed claim set or null
+     */
+    @Nullable protected JWTClaimsSet parseAccessToken(@Nonnull @NotEmpty final AccessToken token) {
         
-        if (revocationCache.isRevoked(RevocationCacheContexts.AUTHORIZATION_CODE, accessTokenClaimsSet.getID())) {
-            log.warn("{} Authorization code {} and all derived tokens have been revoked", getLogPrefix(),
-                    accessTokenClaimsSet.getID());
-            ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_GRANT);
-            return;
+        // Try parsing as a JWT.
+        try {
+            signedJWT = SignedJWT.parse(token.getValue());
+            return signedJWT.getJWTClaimsSet();
+        } catch (final ParseException e1) {
+            
+        }
+
+        // Fall back to opaque.
+        try {
+            opaqueClaimsSet = AccessTokenClaimsSet.parse(token.getValue(), dataSealer);
+            return opaqueClaimsSet.getClaimsSet();
+        } catch (final DataSealerException | ParseException e) {
+            
         }
         
-        log.debug("{} Access token {} validated", getLogPrefix(), accessTokenClaimsSet.getID());
-        getOidcResponseContext().setAuthorizationGrantClaimsSet(accessTokenClaimsSet);
+        return null;
     }
 
 }
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml
index 57d27858..bb60f197 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml
@@ -182,6 +182,14 @@
     <bean id="ValidateScope" class="net.shibboleth.idp.plugin.oidc.op.profile.impl.ValidateScope" scope="prototype"
         p:allowedScopeLookupStrategy="#{getObject('shibboleth.oidc.AllowedScopeStrategy') ?: getObject('shibboleth.oidc.DefaultAllowedScopeStrategy')}" />
 
+    <bean id="ValidateAudience"
+        class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.ValidateAudience" scope="prototype"
+        p:requestedAudienceLookupStrategy-ref="AuthenticationRequestAudienceLookupStrategy"
+        p:allowedAudienceLookupStrategy="#{getObject('shibboleth.oidc.AllowedAudienceStrategy') ?: getObject('shibboleth.oidc.DefaultAllowedAudienceStrategy')}" />
+
+    <bean id="AuthenticationRequestAudienceLookupStrategy"
+        class="net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.DefaultRequestAudienceLookupFunction" />
+
     <bean id="PopulateIDTokenSignatureSigningParameters"
         class="net.shibboleth.idp.plugin.oidc.op.profile.impl.PopulateOIDCSignatureSigningParameters " scope="prototype"
         c:strategy-ref="shibboleth.MessageContextLookup.Inbound"
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-flow.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-flow.xml
index 45b92e20..7578912b 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-flow.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-flow.xml
@@ -50,6 +50,7 @@
         <evaluate expression="ValidateResponseType" />
         <evaluate expression="ValidateCodeChallenge" />
         <evaluate expression="ValidateScope" />
+        <evaluate expression="ValidateAudience" />
         <evaluate expression="SetRequestedClaimsToResponseContext" />
         <evaluate expression="SetRequestedSubjectToResponseContext" />
         <evaluate expression="PopulateIDTokenSignatureSigningParameters" />
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-beans.xml
index 2c50d3e8..bc6ce334 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-beans.xml
@@ -164,15 +164,11 @@
 
     <bean id="ValidateAudience"
         class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.ValidateAudience" scope="prototype"
-        p:requestedAudienceLookupStrategy-ref="TokenRequestAudienceLookupStrategy"
         p:allowedAudienceLookupStrategy="#{getObject('shibboleth.oidc.AllowedAudienceStrategy') ?: getObject('shibboleth.oidc.DefaultAllowedAudienceStrategy')}" />
 
     <bean id="TokenRequestScopeLookupStrategy"
         class="net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.TokenRequestScopeLookupFunction" />
 
-    <bean id="TokenRequestAudienceLookupStrategy"
-        class="net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.TokenRequestAudienceLookupFunction" />
-
     <bean id="IssueIDTokenCondition"
         class="net.shibboleth.idp.plugin.oidc.op.profile.logic.IssueIDTokenCondition" />
 
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml
index e79e5955..a5b4a38f 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml
@@ -24,6 +24,7 @@
         p:forcePKCE="%{idp.oidc.forcePKCE:false}"
         p:allowPKCEPlain="%{idp.oidc.allowPKCEPlain:false}"
         p:iDTokenLifetime="%{idp.oidc.idToken.defaultLifetime:PT1H}"
+        p:accessTokenType="%{idp.oauth2.accessToken.type:}"
         p:accessTokenLifetime="%{idp.oidc.accessToken.defaultLifetime:PT10M}"
         p:refreshTokenLifetime="%{idp.oidc.refreshToken.defaultLifetime:PT2H}"
         p:alwaysIncludedAttributes="%{idp.oidc.alwaysIncludedAttributes:}" />
@@ -40,7 +41,8 @@
         class="net.shibboleth.oidc.profile.config.OIDCUserInfoConfiguration"
         p:issuer-ref="shibboleth.oidc.issuer"
         p:encryptionOptional="%{idp.oidc.encryptionOptional:true}"
-        p:deniedUserInfoAttributes="%{idp.oidc.deniedUserInfoAttributes:}" />
+        p:deniedUserInfoAttributes="%{idp.oidc.deniedUserInfoAttributes:}"
+        p:issuedClaimsValidator-ref="DefaultUserInfoJWTClaimsValidator" />
         
     <bean id="OIDC.Registration" parent="AbstractOIDCProfile" lazy-init="true"
         class="net.shibboleth.oidc.profile.config.OIDCDynamicRegistrationConfiguration"
@@ -214,6 +216,10 @@
             <bean parent="shibboleth.MDDrivenDurationProperty" p:propertyName="iDTokenLifetime"
                 p:defaultValue="%{idp.oidc.idToken.defaultLifetime:PT1H}" />
         </property>
+        <property name="accessTokenTypeLookupStrategy">
+            <bean parent="shibboleth.MDDrivenStringProperty" p:propertyName="accessTokenType"
+                p:defaultValue="%{idp.oidc.accessToken.type:}" />
+        </property>
         <property name="accessTokenLifetimeLookupStrategy">
             <bean parent="shibboleth.MDDrivenDurationProperty" p:propertyName="accessTokenLifetime"
                 p:defaultValue="%{idp.oidc.accessToken.defaultLifetime:PT10M}" />
@@ -309,6 +315,11 @@
                 </property>
             </bean>
         </property>
+        <property name="issuedClaimsValidatorLookupStrategy">
+            <bean parent="shibboleth.MDDrivenBeanProperty" p:propertyName="issuedClaimsValidator"
+                p:propertyType="#{T(net.shibboleth.oidc.jwt.claims.ClaimsValidator)}"
+                p:defaultValue-ref="DefaultUserInfoJWTClaimsValidator" />
+        </property>
     </bean>
         
     <bean id="OIDC.Registration.MDDriven" parent="AbstractMDDrivenOIDCFlowAwareProfile" lazy-init="true"
@@ -500,7 +511,7 @@
         <ref bean="JWTIdentifierClaimsValidator" />
     </util:list>
 
-    <!-- Default issued JWT validation wiring (for introspection/revocation). -->
+    <!-- Default issued JWT validation wiring (for introspection/revocation/UserInfo). -->
 
     <bean id="DefaultIntrospectionJWTClaimsValidator"
         class="net.shibboleth.oidc.security.jwt.claims.impl.ChainingJWTClaimsValidator"
@@ -510,6 +521,10 @@
         class="net.shibboleth.oidc.security.jwt.claims.impl.ChainingJWTClaimsValidator"
         p:claimValidators-ref="RevocationClaimsValidators" />
 
+    <bean id="DefaultUserInfoJWTClaimsValidator"
+        class="net.shibboleth.oidc.security.jwt.claims.impl.ChainingJWTClaimsValidator"
+        p:claimValidators-ref="UserInfoClaimsValidators" />
+
     <bean id="SelfIssuedClaimsValidator"
             class="net.shibboleth.oidc.security.jwt.claims.impl.ExactMatchClaimsValidator"
             p:claimName="iss">
@@ -575,6 +590,42 @@
         </bean>
     </util:list>
 
+    <bean id="OPInAudienceClaimsValidator"
+            class="net.shibboleth.oidc.security.jwt.claims.impl.AudienceClaimsValidator">
+        <property name="audienceLookupStrategy">
+            <bean class="net.shibboleth.utilities.java.support.logic.BiFunctionSupport"
+                factory-method="forFunctionOfFirstArg"
+                    c:_0-ref="shibboleth.ResponderIdLookup.Simple" />
+        </property>
+    </bean>
+
+    <bean id="SelfIssuedClaimsValidator"
+            class="net.shibboleth.oidc.security.jwt.claims.impl.ExactMatchClaimsValidator"
+            p:claimName="iss">
+        <property name="valueToMatchLookupStrategy">
+            <bean class="net.shibboleth.utilities.java.support.logic.BiFunctionSupport"
+                factory-method="forFunctionOfFirstArg"
+                    c:_0-ref="shibboleth.ResponderIdLookup.Simple" />
+        </property>
+    </bean>
+
+    <util:list id="UserInfoClaimsValidators" value-type="net.shibboleth.oidc.jwt.claims.ClaimsValidator">
+        <ref bean="RequiredClaimsValidator" />
+        <ref bean="ExpiryClaimsValidator" />
+        <ref bean="NotBeforeClaimsValidator" />
+        <ref bean="SelfIssuedClaimsValidator" />
+        <bean class="net.shibboleth.oidc.security.jwt.claims.impl.ChainingJWTClaimsValidator" p:requireAll="false">
+            <property name="claimValidators">
+                <list value-type="net.shibboleth.oidc.jwt.claims.ClaimsValidator">
+                    <ref bean="ClientIDClaimsValidator" />
+                    <ref bean="LegacyClientIDClaimsValidator" />
+                </list>
+            </property>
+        </bean>
+        <ref bean="OPInAudienceClaimsValidator" />
+        <ref bean="JWTIDRevocationClaimsValidator" />
+    </util:list>
+
     <!--
     Auto-wiring exposers for credentials to get them loaded into the IdP's relying party config resolver.
     The qualifiers control which auto-wiring point is used.

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


More information about the commits mailing list