[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