[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