[java-idp-oidc] branch main updated: Move JWT claims validation to profile config.
Scott Cantor
cantor.2 at osu.edu
Mon Jan 3 16:12:31 UTC 2022
This is an automated email from the git hooks/post-receive script.
scantor pushed a commit to branch main
in repository java-idp-oidc.
View the commit online:
http://git.shibboleth.net/view/?p=java-idp-oidc.git;a=commit;h=bf4f16903ad8c638879a641f2ed9a8edb01fb41c
The following commit(s) were added to refs/heads/main by this push:
new bf4f1690 Move JWT claims validation to profile config.
bf4f1690 is described below
commit bf4f16903ad8c638879a641f2ed9a8edb01fb41c
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Mon Jan 3 11:12:28 2022 -0500
Move JWT claims validation to profile config.
---
.../oidc/op/authn/impl/JWTCredentialValidator.java | 39 +++++++------
.../authn/OAuth2Client/OAuth2Client-beans.xml | 55 +------------------
.../idp/service/relying-party/postconfig.xml | 64 +++++++++++++++++++++-
.../op/authn/impl/JWTCredentialValidatorTest.java | 24 ++++++--
4 files changed, 102 insertions(+), 80 deletions(-)
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/authn/impl/JWTCredentialValidator.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/authn/impl/JWTCredentialValidator.java
index 9bb5d6ee..c8ada4dd 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/authn/impl/JWTCredentialValidator.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/authn/impl/JWTCredentialValidator.java
@@ -33,11 +33,10 @@ import net.shibboleth.idp.plugin.oidc.op.authn.context.OAuth2ClientAuthenticatio
import net.shibboleth.idp.profile.context.RelyingPartyContext;
import net.shibboleth.oidc.jwt.claims.ClaimsValidator;
import net.shibboleth.oidc.jwt.claims.JWTValidationException;
+import net.shibboleth.oidc.profile.config.navigate.ClaimsValidatorLookupFunction;
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.annotation.constraint.ThreadSafeAfterInit;
-import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
import net.shibboleth.utilities.java.support.component.ComponentSupport;
import net.shibboleth.utilities.java.support.logic.Constraint;
@@ -73,8 +72,8 @@ public class JWTCredentialValidator extends AbstractCredentialValidator {
/** Strategy used to locate the {@link SecurityParametersContext} to use for verification. */
@Nonnull private Function<ProfileRequestContext,SecurityParametersContext> securityParametersLookupStrategy;
- /** The claims validation to be applied for validating the incoming JWTs. */
- @NonnullAfterInit private ClaimsValidator claimsValidation;
+ /** Strategy used to obtain {@link ClaimsValidator}. */
+ @Nonnull private Function<ProfileRequestContext,ClaimsValidator> claimsValidatorLookupStrategy;
/** Whether to save the JWT in the Java Subject's public credentials. */
private boolean saveTokenToCredentialSet;
@@ -87,6 +86,8 @@ public class JWTCredentialValidator extends AbstractCredentialValidator {
// PRC -> RP -> SPC
securityParametersLookupStrategy = new ChildContextLookup<>(SecurityParametersContext.class).compose(
new ChildContextLookup<>(RelyingPartyContext.class));
+
+ claimsValidatorLookupStrategy = new ClaimsValidatorLookupFunction();
}
/**
@@ -116,14 +117,16 @@ public class JWTCredentialValidator extends AbstractCredentialValidator {
}
/**
- * Set the claims validator used for validating the incoming JWTs.
+ * Set the strategy used to locate {@link ClaimsValidator} used.
*
- * @param validator claims validator
+ * @param strategy lookup strategy
*/
- public void setClaimsValidation(@Nonnull final ClaimsValidator validator) {
+ public void setClaimsValidatorLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext,ClaimsValidator> strategy) {
ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
- claimsValidation = Constraint.isNotNull(validator, "JWTClaimsValidation cannot be null");
+ claimsValidatorLookupStrategy =
+ Constraint.isNotNull(strategy, "ClaimsValidator lookup strategy cannot be null");
}
/**
@@ -139,16 +142,6 @@ public class JWTCredentialValidator extends AbstractCredentialValidator {
saveTokenToCredentialSet = flag;
}
- /** {@inheritDoc} */
- @Override
- protected void doInitialize() throws ComponentInitializationException {
- super.doInitialize();
-
- if (claimsValidation == null) {
- throw new ComponentInitializationException("ClaimsValidator cannot be null");
- }
- }
-
// Checkstyle: CyclomaticComplexity OFF
/** {@inheritDoc} */
@Override
@@ -228,9 +221,15 @@ public class JWTCredentialValidator extends AbstractCredentialValidator {
protected void validateJWTClaims(@Nonnull final ProfileRequestContext profileRequestContext,
@Nonnull final SignedJWT jwt, @Nonnull final ClientID clientId)
throws ParseException, JWTValidationException {
+
+ final ClaimsValidator validator = claimsValidatorLookupStrategy.apply(profileRequestContext);
+ if (validator == null) {
+ log.warn("{} JWT validation failed for client '{}': No ClaimsValidator found in configuration",
+ getLogPrefix(), clientId);
+ throw new JWTValidationException("No ClaimsValidator found in configuration");
+ }
final JWTClaimsSet claimsSet;
-
try {
claimsSet = jwt.getJWTClaimsSet();
} catch (final ParseException e) {
@@ -239,7 +238,7 @@ public class JWTCredentialValidator extends AbstractCredentialValidator {
}
try {
- claimsValidation.validate(claimsSet, profileRequestContext);
+ validator.validate(claimsSet, profileRequestContext);
} catch (final JWTValidationException e) {
log.warn("{} JWT validation failed for client '{}': {}", getLogPrefix(), clientId, e.getMessage());
throw e;
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OAuth2Client/OAuth2Client-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OAuth2Client/OAuth2Client-beans.xml
index a5f9edd3..fd0cdfcf 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OAuth2Client/OAuth2Client-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OAuth2Client/OAuth2Client-beans.xml
@@ -60,60 +60,9 @@
<bean class="net.shibboleth.idp.plugin.oidc.op.authn.impl.OIDCClientInfoCredentialValidator"
p:id="oauth2-clientinfo" />
<bean class="net.shibboleth.idp.plugin.oidc.op.authn.impl.JWTCredentialValidator"
- p:id="oauth2-jwt"
- p:claimsValidation-ref="#{'%{idp.auth.OAuth2Client.JWTValidation:DefaultJWTClaimsValidation}'.trim()}" />
+ p:id="oauth2-jwt" />
</util:list>
-
- <!-- JWT validation wiring. -->
-
- <bean id="DefaultJWTClaimsValidation"
- class="net.shibboleth.oidc.security.jwt.claims.impl.ChainingJWTClaimsValidator"
- p:claimValidators-ref="ClaimsValidators" />
-
- <bean id="ClientIDFromOIDCMetadataContextLookupFunction"
- class="net.shibboleth.idp.plugin.oidc.op.profile.logic.ClientIDFromOIDCMetadataContextLookupFunction" />
-
- <bean id="ExpiryClaimsValidator"
- class="net.shibboleth.oidc.security.jwt.claims.impl.ExpiryClaimsValidator"
- p:clockSkew="%{idp.policy.clockSkew:PT1M}" />
-
- <bean id="IssuedAtClaimsValidator"
- class="net.shibboleth.oidc.security.jwt.claims.impl.IssuedAtClaimsValidator"
- p: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="ClientIDFromOIDCMetadataContextLookupFunction" />
-
- <bean id="SubjectClaimsValidator"
- class="net.shibboleth.oidc.security.jwt.claims.impl.ExactMatchClaimsValidator"
- p:claimName="sub" p:valueToMatchLookupStrategy-ref="ClientIDFromOIDCMetadataContextLookupFunction" />
-
- <bean id="AudienceClaimsValidator"
- class="net.shibboleth.oidc.security.jwt.claims.impl.AudienceClaimsValidator">
- <property name="audienceLookupStrategy">
- <bean parent="shibboleth.BiFunctions.Expression"
- c:expression="#custom.getRequestURL().toString()"
- p:customObject-ref="shibboleth.HttpServletRequest" />
- </property>
- </bean>
-
- <bean id="JWTIdentifierClaimsValidator"
- class="net.shibboleth.oidc.security.jwt.claims.impl.JWTIdentifierClaimsValidator"
- p:clockSkew="%{idp.policy.clockSkew:PT1M}"
- p:replayCache-ref="shibboleth.ReplayCache" />
-
- <util:list id="ClaimsValidators" value-type="net.shibboleth.oidc.jwt.claims.ClaimsValidator">
- <ref bean="ExpiryClaimsValidator" />
- <ref bean="IssuedAtClaimsValidator" />
- <ref bean="IssuerClaimsValidator" />
- <ref bean="SubjectClaimsValidator" />
- <ref bean="AudienceClaimsValidator" />
- <ref bean="JWTIdentifierClaimsValidator" />
- </util:list>
-
+
<!-- Validator parent beans -->
<bean id="shibboleth.JAASValidator"
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 0f9df71c..913f806b 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
@@ -21,6 +21,7 @@
<bean id="AbstractOIDCSSOProfile" parent="AbstractOIDCProfile" abstract="true"
p:issuer-ref="issuer"
p:tokenEndpointAuthMethods="%{idp.oidc.tokenEndpointAuthMethods:client_secret_basic,client_secret_post,client_secret_jwt,private_key_jwt}"
+ p:claimsValidator-ref="DefaultJWTClaimsValidator"
p:forcePKCE="%{idp.oidc.forcePKCE:false}"
p:allowPKCEPlain="%{idp.oidc.allowPKCEPlain:false}"
p:iDTokenLifetime="%{idp.oidc.idToken.defaultLifetime:PT1H}"
@@ -49,7 +50,8 @@
p:issuer-ref="issuer"
p:registrationValidityPeriod="%{idp.oidc.dynreg.defaultRegistrationValidity:PT24H}"
p:secretExpirationPeriod="%{idp.oidc.dynreg.defaultSecretExpiration:P12M}"
- p:tokenEndpointAuthMethods="%{idp.oidc.dynreg.tokenEndpointAuthMethods:client_secret_basic,client_secret_post,client_secret_jwt,private_key_jwt}" />
+ p:tokenEndpointAuthMethods="%{idp.oidc.dynreg.tokenEndpointAuthMethods:client_secret_basic,client_secret_post,client_secret_jwt,private_key_jwt}"
+ p:claimsValidator-ref="DefaultJWTClaimsValidator" />
<bean id="OIDC.Configuration" parent="AbstractOIDCProfile" lazy-init="true"
class="net.shibboleth.oidc.profile.config.OIDCProviderInformationConfiguration"
@@ -57,7 +59,8 @@
<bean id="OAUTH2.Revocation" parent="AbstractOIDCProfile" lazy-init="true"
class="net.shibboleth.oidc.profile.oauth2.config.OAuth2TokenRevocationConfiguration"
- p:tokenEndpointAuthMethods="%{idp.oidc.dynreg.tokenEndpointAuthMethods:client_secret_basic,client_secret_post,client_secret_jwt,private_key_jwt}" />
+ p:tokenEndpointAuthMethods="%{idp.oidc.dynreg.tokenEndpointAuthMethods:client_secret_basic,client_secret_post,client_secret_jwt,private_key_jwt}"
+ p:claimsValidator-ref="DefaultJWTClaimsValidator" />
<bean id="OIDC.Keyset" parent="AbstractOIDCProfile" lazy-init="true"
class="net.shibboleth.oidc.profile.config.OIDCPublishKeySetConfiguration"
@@ -65,7 +68,8 @@
<bean id="OAUTH2.Introspection" parent="AbstractOIDCProfile" lazy-init="true"
class="net.shibboleth.oidc.profile.oauth2.config.OAuth2TokenIntrospectionConfiguration"
- p:tokenEndpointAuthMethods="%{idp.oidc.dynreg.tokenEndpointAuthMethods:client_secret_basic,client_secret_post,client_secret_jwt,private_key_jwt}" />
+ p:tokenEndpointAuthMethods="%{idp.oidc.dynreg.tokenEndpointAuthMethods:client_secret_basic,client_secret_post,client_secret_jwt,private_key_jwt}"
+ p:claimsValidator-ref="DefaultJWTClaimsValidator" />
<!-- Metadata-driven variants. -->
@@ -78,6 +82,11 @@
</bean>
<bean id="AbstractMDDrivenOAuthClientAuthenticatableProfile" parent="AbstractMDDrivenOIDCProfile" abstract="true">
+ <property name="claimsValidatorLookupStrategy">
+ <bean parent="shibboleth.MDDrivenBeanProperty" p:propertyName="claimsValidator"
+ p:propertyType="#{T(net.shibboleth.oidc.jwt.claims.ClaimsValidator)}"
+ p:defaultValue-ref="DefaultJWTClaimsValidator" />
+ </property>
<property name="forceAuthnPredicate">
<bean class="net.shibboleth.utilities.java.support.logic.PredicateSupport" factory-method="fromFunction">
<constructor-arg>
@@ -352,6 +361,55 @@
</property>
</bean>
+ <!-- Default JWT validation wiring. -->
+
+ <bean id="DefaultJWTClaimsValidator"
+ class="net.shibboleth.oidc.security.jwt.claims.impl.ChainingJWTClaimsValidator"
+ p:claimValidators-ref="ClaimsValidators" />
+
+ <bean id="ClientIDFromOIDCMetadataContextLookupFunction"
+ class="net.shibboleth.idp.plugin.oidc.op.profile.logic.ClientIDFromOIDCMetadataContextLookupFunction" />
+
+ <bean id="ExpiryClaimsValidator"
+ class="net.shibboleth.oidc.security.jwt.claims.impl.ExpiryClaimsValidator"
+ p:clockSkew="%{idp.policy.clockSkew:PT1M}" />
+
+ <bean id="IssuedAtClaimsValidator"
+ class="net.shibboleth.oidc.security.jwt.claims.impl.IssuedAtClaimsValidator"
+ p: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="ClientIDFromOIDCMetadataContextLookupFunction" />
+
+ <bean id="SubjectClaimsValidator"
+ class="net.shibboleth.oidc.security.jwt.claims.impl.ExactMatchClaimsValidator"
+ p:claimName="sub" p:valueToMatchLookupStrategy-ref="ClientIDFromOIDCMetadataContextLookupFunction" />
+
+ <bean id="AudienceClaimsValidator"
+ class="net.shibboleth.oidc.security.jwt.claims.impl.AudienceClaimsValidator">
+ <property name="audienceLookupStrategy">
+ <bean parent="shibboleth.BiFunctions.Expression"
+ c:expression="#custom.getRequestURL().toString()"
+ p:customObject-ref="shibboleth.HttpServletRequest" />
+ </property>
+ </bean>
+
+ <bean id="JWTIdentifierClaimsValidator"
+ class="net.shibboleth.oidc.security.jwt.claims.impl.JWTIdentifierClaimsValidator"
+ p:clockSkew="%{idp.policy.clockSkew:PT1M}"
+ p:replayCache-ref="shibboleth.ReplayCache" />
+
+ <util:list id="ClaimsValidators" value-type="net.shibboleth.oidc.jwt.claims.ClaimsValidator">
+ <ref bean="ExpiryClaimsValidator" />
+ <ref bean="IssuedAtClaimsValidator" />
+ <ref bean="IssuerClaimsValidator" />
+ <ref bean="SubjectClaimsValidator" />
+ <ref bean="AudienceClaimsValidator" />
+ <ref bean="JWTIdentifierClaimsValidator" />
+ </util:list>
<!--
Auto-wiring exposers for credentials to get them loaded into the IdP's relying party config resolver.
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/authn/impl/JWTCredentialValidatorTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/authn/impl/JWTCredentialValidatorTest.java
index ce8c780f..85ddf769 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/authn/impl/JWTCredentialValidatorTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/authn/impl/JWTCredentialValidatorTest.java
@@ -71,8 +71,11 @@ import net.shibboleth.idp.authn.principal.UsernamePrincipal;
import net.shibboleth.idp.plugin.oidc.op.authn.context.OAuth2ClientAuthenticationContext;
import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCMetadataContext;
import net.shibboleth.idp.plugin.oidc.op.profile.logic.ClientIDFromOIDCMetadataContextLookupFunction;
+import net.shibboleth.idp.profile.context.RelyingPartyContext;
import net.shibboleth.idp.profile.testing.ActionTestingSupport;
import net.shibboleth.oidc.jwt.claims.ClaimsValidator;
+import net.shibboleth.oidc.profile.config.OIDCTokenConfiguration;
+import net.shibboleth.oidc.profile.oauth2.config.AbstractOAuth2ClientAuthenticableProfileConfiguration;
import net.shibboleth.oidc.security.credential.BasicJWKCredential;
import net.shibboleth.oidc.security.impl.OIDCSignatureValidationParameters;
import net.shibboleth.oidc.security.jwt.claims.impl.AudienceClaimsValidator;
@@ -96,6 +99,7 @@ public class JWTCredentialValidatorTest extends BaseAuthenticationContextTest {
RSAPrivateKey rsaPrivateKey;
RSAPublicKey rsaPublicKey;
+ private ClaimsValidator claimsValidator;
private JWTCredentialValidator validator;
private ValidateCredentials action;
@@ -120,17 +124,21 @@ public class JWTCredentialValidatorTest extends BaseAuthenticationContextTest {
throw new ComponentInitializationException(e);
}
- ReplayCache replayCache = new ReplayCache();
- MemoryStorageService storageService = new MemoryStorageService();
+ final ReplayCache replayCache = new ReplayCache();
+ final MemoryStorageService storageService = new MemoryStorageService();
storageService.setId("mockId");
storageService.initialize();
replayCache.setStorage(storageService);
+ claimsValidator =
+ constructClaimsValidator((HttpServletRequest) src.getExternalContext().getNativeRequest(), replayCache);
+ final OIDCTokenConfiguration profile = new OIDCTokenConfiguration();
+ profile.setClaimsValidator(claimsValidator);
+ prc.getSubcontext(RelyingPartyContext.class).setProfileConfig(profile);
+
validator = new JWTCredentialValidator();
validator.setId("test");
validator.setSecurityParametersLookupStrategy(new ChildContextLookup<>(SecurityParametersContext.class));
- validator.setClaimsValidation(
- constructClaimsValidator((HttpServletRequest) src.getExternalContext().getNativeRequest(), replayCache));
validator.initialize();
action = new ValidateCredentials();
@@ -300,6 +308,14 @@ public class JWTCredentialValidatorTest extends BaseAuthenticationContextTest {
return jwt;
}
+ @Test
+ public void testNoClaimsValidator() throws Exception {
+ ((AbstractOAuth2ClientAuthenticableProfileConfiguration) prc.getSubcontext(
+ RelyingPartyContext.class).getProfileConfig()).setClaimsValidator(null);
+ testFailingJwtAuth(ClientAuthenticationMethod.CLIENT_SECRET_JWT,
+ createSecretJWT(validClaimsSet()), false, true);
+ }
+
@Test
public void testSecretJwt() throws JOSEException, NoSuchAlgorithmException {
initializeTokenRequest(ClientAuthenticationMethod.CLIENT_SECRET_JWT, createSecretJWT(validClaimsSet()), true);
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list