[java-oidc-common] branch main updated: JCOMOIDC-1 - JWT Support

Phil Smart philip.smart at jisc.ac.uk
Fri Jan 29 10:58:00 UTC 2021


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

philsmart pushed a commit to branch main
in repository java-oidc-common.

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

The following commit(s) were added to refs/heads/main by this push:
       new  f32cb80   JCOMOIDC-1 - JWT Support
f32cb80 is described below

commit f32cb8016d6bd7065e31ddb10854a0d3ec97b371
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Fri Jan 29 10:54:40 2021 +0000

    JCOMOIDC-1 - JWT Support
    
    Add JWT claims validation framework and validators to support OIDC
    claims validation - OpenID Connect core 1.0 section 3.1.3.7.
    
    Employs strategies for exploiting runtime information in the context
    or claims set.
    
    https://issues.shibboleth.net/jira/browse/JCOMOIDC-1
---
 .../oidc/jwt/claims/AbstractClaimsValidator.java   |  82 +++++++
 .../oidc/jwt/claims/ClaimsValidator.java           |  55 +++++
 .../oidc/jwt/claims/JWTClaimsValidation.java       |  34 +++
 .../oidc/jwt/claims/JWTValidationException.java    |  43 ++++
 oidc-common-crypto-impl/pom.xml                    |  25 +-
 .../jwt/claims/impl/AudienceClaimsValidator.java   |  97 ++++++++
 .../impl/AuthenticationTimeClaimsValidator.java    | 139 +++++++++++
 .../claims/impl/ChainingJWTClaimsValidation.java   | 100 ++++++++
 .../jwt/claims/impl/ExactMatchClaimsValidator.java | 111 +++++++++
 .../jwt/claims/impl/ExpiryClaimsValidator.java     |  87 +++++++
 .../ForcedAuthenticationActivationCondition.java   |  49 ++++
 .../security/jwt/claims/impl/IDTokenClaims.java    |  66 +++++
 .../jwt/claims/impl/IssuedAtClaimsValidator.java   |  89 +++++++
 .../oidc/security/jwt/claims/impl/JWTClaims.java   |  74 ++++++
 .../impl/NonceValidationActiviationCondition.java  |  43 ++++
 .../jwt/claims/impl/NotBeforeClaimsValidator.java  |  82 +++++++
 .../jwt/claims/impl/ProhibitedClaimsValidator.java |  84 +++++++
 .../jwt/claims/impl/RequiredClaimsValidator.java   |  81 +++++++
 .../security/jwt/claims/impl/package-info.java     |  19 ++
 .../claims/impl/AbstractClaimsValidatorTest.java   |  62 +++++
 .../claims/impl/AudienceClaimsValidatorTest.java   | 115 +++++++++
 .../AuthenticationTimeClaimsValidatorTest.java     | 136 +++++++++++
 .../impl/ChainingJWTClaimsValidationTest.java      | 267 +++++++++++++++++++++
 .../claims/impl/ExactMatchClaimsValidatorTest.java | 153 ++++++++++++
 .../jwt/claims/impl/ExpiryClaimsValidatorTest.java |  92 +++++++
 .../claims/impl/IssuedAtClaimsValidatorTest.java   |  99 ++++++++
 .../claims/impl/NotBeforeClaimsValidatorTest.java  |  99 ++++++++
 .../claims/impl/ProhibitedClaimsValidatorTest.java |  84 +++++++
 .../claims/impl/RequiredClaimsValidatorTest.java   |  83 +++++++
 .../security/jwt/claims/impl/package-info.java     |  19 ++
 ...seStorageServiceClientInformationComponent.java |   2 +
 31 files changed, 2569 insertions(+), 2 deletions(-)

diff --git a/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/jwt/claims/AbstractClaimsValidator.java b/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/jwt/claims/AbstractClaimsValidator.java
new file mode 100644
index 0000000..336f52a
--- /dev/null
+++ b/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/jwt/claims/AbstractClaimsValidator.java
@@ -0,0 +1,82 @@
+/*
+ * 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.oidc.jwt.claims;
+
+import java.util.function.BiPredicate;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.utilities.java.support.component.AbstractIdentifiableInitializableComponent;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/**
+ * Base class for {@link ClaimsValidator claims validators}.
+ */
+public abstract class AbstractClaimsValidator  
+            extends AbstractIdentifiableInitializableComponent implements ClaimsValidator{
+    
+    /** Does this validator apply to this request? Default is true. */
+    @Nonnull private BiPredicate<ProfileRequestContext, JWTClaimsSet> activationCondition;
+    
+    /** Constructor. */
+    public AbstractClaimsValidator() {
+        //default is always true
+        activationCondition = (prc,claims) -> true;
+    }
+    
+    /**
+     * Set an activation condition for this validator.
+     * 
+     * @param condition condition to set
+     */
+    public void setActivationCondition(@Nonnull final BiPredicate<ProfileRequestContext, JWTClaimsSet> condition) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        
+        activationCondition = Constraint.isNotNull(condition, "Activation condition cannot be null");
+    }
+    
+    /** {@inheritDoc} */
+    @Override
+    public void validate(@Nonnull final JWTClaimsSet claims, @Nonnull final ProfileRequestContext context) 
+            throws JWTValidationException {
+        
+        if (!activationCondition.test(context,claims)) {
+            //not active for this request
+            return;
+        }
+        doValidate(claims,context);
+    }
+
+    /**
+     * Perform validation of the given claims supported by the supplied context.
+     * 
+     * @param claims the claims to validate.
+     * @param context the profile request context.
+     * 
+     * @throws JWTValidationException when validation is unsuccessful due to a failed attempt
+     */
+    protected abstract void doValidate(@Nonnull final JWTClaimsSet claims, 
+            @Nonnull final ProfileRequestContext context) throws JWTValidationException;
+
+}
diff --git a/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/jwt/claims/ClaimsValidator.java b/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/jwt/claims/ClaimsValidator.java
new file mode 100644
index 0000000..edf079c
--- /dev/null
+++ b/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/jwt/claims/ClaimsValidator.java
@@ -0,0 +1,55 @@
+/*
+ * 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.oidc.jwt.claims;
+
+import javax.annotation.Nonnull;
+import javax.annotation.concurrent.Immutable;
+import javax.annotation.concurrent.ThreadSafe;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.utilities.java.support.component.IdentifiableComponent;
+
+/**
+ * High-level API for validating a JWT's claims set.
+ * 
+ * <p>Instances of this interface must be stateless.</p>
+ * 
+ */
+ at ThreadSafe
+ at Immutable
+public interface ClaimsValidator extends IdentifiableComponent{
+    
+    /**
+     * Validate all, or part of, the given JWT claims set. Can take supporting information
+     * from the given context tree.
+     *  
+     * <p>Throws an exception if validation fails.</p>
+     * 
+     * @param claims the claims to validate.
+     * @param context the profile request context.
+     * 
+     * @throws JWTValidationException when validation is unsuccessful due to a failed attempt
+     */
+    void validate(@Nonnull final JWTClaimsSet claims, @Nonnull final ProfileRequestContext context)
+        throws JWTValidationException;
+
+}
diff --git a/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/jwt/claims/JWTClaimsValidation.java b/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/jwt/claims/JWTClaimsValidation.java
new file mode 100644
index 0000000..eed0540
--- /dev/null
+++ b/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/jwt/claims/JWTClaimsValidation.java
@@ -0,0 +1,34 @@
+/*
+ * 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.oidc.jwt.claims;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+public interface JWTClaimsValidation {
+    
+    
+    void validate(@Nullable final JWTClaimsSet claims, @Nonnull final ProfileRequestContext context)
+        throws JWTValidationException;
+
+}
diff --git a/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/jwt/claims/JWTValidationException.java b/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/jwt/claims/JWTValidationException.java
new file mode 100644
index 0000000..d3a0b5c
--- /dev/null
+++ b/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/jwt/claims/JWTValidationException.java
@@ -0,0 +1,43 @@
+package net.shibboleth.oidc.jwt.claims;
+
+import javax.annotation.Nullable;
+
+public class JWTValidationException extends Exception{
+
+    /** Serial version UID. */
+    private static final long serialVersionUID = -7779377025440142747L;
+    
+    /** Constructor. */
+    public JWTValidationException() {
+        super();
+    }
+
+    /**
+     * Constructor.
+     * 
+     * @param message exception message
+     */
+    public JWTValidationException(@Nullable final String message) {
+        super(message);
+    }
+
+    /**
+     * Constructor.
+     * 
+     * @param wrappedException exception to be wrapped by this one
+     */
+    public JWTValidationException(@Nullable final Exception wrappedException) {
+        super(wrappedException);
+    }
+
+    /**
+     * Constructor.
+     * 
+     * @param message exception message
+     * @param wrappedException exception to be wrapped by this one
+     */
+    public JWTValidationException(@Nullable final String message, @Nullable final Exception wrappedException) {
+        super(message, wrappedException);
+    }
+
+}
diff --git a/oidc-common-crypto-impl/pom.xml b/oidc-common-crypto-impl/pom.xml
index ee66b8a..ab977ec 100644
--- a/oidc-common-crypto-impl/pom.xml
+++ b/oidc-common-crypto-impl/pom.xml
@@ -20,20 +20,35 @@
     
     <dependencies>
         <dependency>
-            <groupId>net.shibboleth.oidc</groupId>
+            <groupId>${project.groupId}</groupId>
             <artifactId>oidc-common-crypto-api</artifactId>
             <scope>provided</scope>
         </dependency>
         <dependency>
-            <groupId>org.opensaml</groupId>
+            <groupId>${opensaml.groupId}</groupId>
             <artifactId>opensaml-saml-api</artifactId>
             <scope>provided</scope>
+        </dependency>
+         <dependency>
+            <groupId>${idp.groupId}</groupId>
+            <artifactId>idp-authn-api</artifactId>
+            <scope>provided</scope>
         </dependency>
         <dependency>
             <groupId>com.nimbusds</groupId>
             <artifactId>nimbus-jose-jwt</artifactId>
             <scope>compile</scope>
         </dependency>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>javax.servlet-api</artifactId>
+            <scope>provided</scope>
+        </dependency>
+         <dependency>
+            <groupId>${spring.groupId}</groupId>
+            <artifactId>spring-test</artifactId>
+            <scope>test</scope>
+        </dependency>
         <dependency>
             <groupId>${opensaml.groupId}</groupId>
             <artifactId>opensaml-core</artifactId>
@@ -41,6 +56,12 @@
             <type>test-jar</type>
             <scope>test</scope>
         </dependency>
+         <dependency>
+            <groupId>${idp.groupId}</groupId>
+            <artifactId>idp-profile-api</artifactId>
+            <scope>test</scope>
+            <type>test-jar</type>
+        </dependency>
     </dependencies>
     
     <build>
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/AudienceClaimsValidator.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/AudienceClaimsValidator.java
new file mode 100644
index 0000000..230059b
--- /dev/null
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/AudienceClaimsValidator.java
@@ -0,0 +1,97 @@
+/* 
+ * 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.oidc.security.jwt.claims.impl;
+
+import java.util.List;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.oidc.jwt.claims.AbstractClaimsValidator;
+import net.shibboleth.oidc.jwt.claims.JWTValidationException;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
+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;
+
+/**
+ * Verifies the Audience (aud) claim contains the client_id of this client (as registered at the issuer). See
+ * section 3.1.3.7 of OpenID Connect core 1.0. The audience is determined at runtime using an appropriate strategy.
+ */
+ at ThreadSafeAfterInit
+public class AudienceClaimsValidator extends AbstractClaimsValidator{
+    
+    /** Strategy to find the audience value from the context.*/
+    @NonnullAfterInit private Function<ProfileRequestContext,String> audienceLookupStrategy;
+    
+    
+    /** {@inheritDoc} */
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+        
+        if (audienceLookupStrategy == null) {
+            throw new ComponentInitializationException("Audience lookup strategy can not be null");
+        }
+    }
+    
+    /**
+     * Set the audience lookup strategy.
+     * 
+     * @param strategy the strategy.
+     */
+    public void setAudienceLookupStrategy(@Nonnull final Function<ProfileRequestContext,String> strategy) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        
+        audienceLookupStrategy = Constraint.isNotNull(strategy, "Audience lookup strategy can not be null");
+    }
+
+    @Override
+    protected void doValidate(@Nonnull final JWTClaimsSet claims, @Nonnull final ProfileRequestContext context) 
+               throws JWTValidationException {
+        
+        final String acceptedAudience = audienceLookupStrategy.apply(context);
+        if (acceptedAudience == null) {
+            throw new JWTValidationException("Audience value not present in the context");
+        }
+        
+        final List<String> audList = claims.getAudience();
+        if (audList != null && !audList.isEmpty()) {
+            boolean audMatch = false;
+            for (final String aud : audList) {
+                if (acceptedAudience.equals(aud)) {
+                    audMatch = true;
+                    break;
+                }
+            }
+            if (!audMatch) {
+                throw new JWTValidationException("JWT audience rejected: " + audList);
+            }
+        } else {
+            throw new JWTValidationException("JWT missing required audience");
+        }
+        
+    }
+
+}
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/AuthenticationTimeClaimsValidator.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/AuthenticationTimeClaimsValidator.java
new file mode 100644
index 0000000..4e540c5
--- /dev/null
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/AuthenticationTimeClaimsValidator.java
@@ -0,0 +1,139 @@
+/*
+ * 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.oidc.security.jwt.claims.impl;
+
+import java.text.ParseException;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Date;
+import java.util.function.Predicate;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Predicates;
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.oidc.jwt.claims.AbstractClaimsValidator;
+import net.shibboleth.oidc.jwt.claims.JWTValidationException;
+import net.shibboleth.utilities.java.support.annotation.constraint.ThreadSafeAfterInit;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/**
+ * Verifies the auth_time (when the End-User authentication took place) is within a valid expiration window.
+ * <p>Uses a predicate to determine if the auth_time was request e.g. was explicitly requested, or the max_age
+ * claim was requested. Defaults to true.</p>
+ */
+ at ThreadSafeAfterInit
+public class AuthenticationTimeClaimsValidator extends AbstractClaimsValidator {
+    
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(AuthenticationTimeClaimsValidator.class);
+    
+    /** 
+     * If request, the amount of time for which a token is valid 
+     * after if it was issued. (Default value: 60 seconds) 
+     */
+    @Nonnull private Duration authnLifetime;
+    
+    /**
+     *  Has the auth_time been requested, either explicitly or from the max_age parameter?
+     *  Defaults to true. 
+     */ 
+    @Nonnull private Predicate<ProfileRequestContext> requested;
+    
+    /** Constructor.*/
+    public AuthenticationTimeClaimsValidator() {
+        authnLifetime = Duration.ofSeconds(60);
+        requested = Predicates.alwaysTrue();
+    }
+
+    
+    /**
+     * Has the auth_time been request e.g. explicitly, or by using the max_age parameter.
+     * 
+     * @param isRequested has auth_time been requested.
+     */
+    public void setRequested(final Predicate<ProfileRequestContext> isRequested) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        
+        requested = isRequested;
+    }
+    
+    /**
+     * Sets the amount of time for which a token is valid from when the original authentication took place.
+     * 
+     * @param lifetime amount of time for which a token is valid
+     */
+    public void setAuthnLifetime(@Nonnull final Duration lifetime) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        
+        Constraint.isNotNull(lifetime, "Token authentication lifetime cannot be null");
+        Constraint.isFalse(lifetime.isNegative(), "Token authentication lifetime cannot be negative");
+        
+        authnLifetime = lifetime;
+    }
+
+    @Override
+    public void doValidate(@Nonnull final JWTClaimsSet claimsSet, @Nonnull final ProfileRequestContext context) 
+            throws JWTValidationException {
+       
+        if (!requested.test(context)) {
+            //not requested, so nothing to check.
+            return;
+        } else {
+            
+            try { 
+                final Date authTimeDate = claimsSet.getDateClaim(IDTokenClaims.AUTHENTICATION_TIME.getClaimName());
+                if (authTimeDate == null) {
+                    throw new JWTValidationException("No authentication time found in token");
+                }
+                final Instant authTime = authTimeDate.toInstant();
+                final Instant now = Instant.now();
+                final Instant expiration = authTime.plus(authnLifetime);
+                
+                // Check time of authentication wasn't in the future
+                if (authTime.isAfter(now)) {
+                    log.warn("Authentication is not yet valid: auth_time was {}, latest valid is: {}",
+                            authTime, now);
+                    throw new JWTValidationException("JWT token authentication time is not yet valid");
+                }
+
+                // Check time of authentication has not expired
+                if (expiration.isBefore(now)) {
+                    log.warn(
+                            "Authentication has expired: auth_time was '{}', "
+                            + "expired at: '{}', current time: '{}'",
+                            authTime, expiration, now);
+                    throw new JWTValidationException("JWT token authentication time has expired");
+                }
+                //is OK.
+                
+            } catch (final ParseException e) {
+                throw new JWTValidationException("Autentication forced, but no authentication time found in token",e);
+            }
+        }
+        
+    }
+
+}
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/ChainingJWTClaimsValidation.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/ChainingJWTClaimsValidation.java
new file mode 100644
index 0000000..47588e8
--- /dev/null
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/ChainingJWTClaimsValidation.java
@@ -0,0 +1,100 @@
+/*
+ * 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.oidc.security.jwt.claims.impl;
+
+import java.util.Collections;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.oidc.jwt.claims.ClaimsValidator;
+import net.shibboleth.oidc.jwt.claims.JWTClaimsValidation;
+import net.shibboleth.oidc.jwt.claims.JWTValidationException;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
+import net.shibboleth.utilities.java.support.component.AbstractInitializableComponent;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+
+/**
+ * A {@link JWTClaimsValidation JWT claims validation} implementation that validates a JWT claims set from a chain 
+ * of configured validators. Validation terminates when one of the validators throws a {@link JWTValidationException}. 
+ * If no {@link JWTValidationException} is thrown, the claims set is 'valid'.
+ * 
+ * <p>Note, does not represent a chain of responsibility pattern, despite the name.</p>
+ */
+public class ChainingJWTClaimsValidation extends AbstractInitializableComponent implements JWTClaimsValidation {
+    
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(ChainingJWTClaimsValidation.class);
+    
+    /** List of claim validators. Ordering is not important.*/
+    @Nonnull @NonnullElements private List<ClaimsValidator> claimValidators;
+    
+    
+    /** {@inheritDoc} */
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+        
+        if (claimValidators == null) {
+            throw new ComponentInitializationException("List of claims validators can not be null");
+        }
+    }
+    
+    /**
+     * Set the list of validators to use.
+     * 
+     * @param validators validators to use
+     */
+    public void setClaimValidators(@Nullable @NonnullElements final List<ClaimsValidator> validators) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        
+        if (validators != null) {
+            claimValidators = List.copyOf(validators);
+        } else {
+            claimValidators = Collections.emptyList();
+        }
+    }
+
+    @Override
+    public void validate(@Nullable final JWTClaimsSet claims, @Nonnull final ProfileRequestContext context) 
+        throws JWTValidationException {       
+        
+        if (claims == null) {
+            log.trace("No claims to verify, nothing todo");
+            return;
+        }
+        log.trace("Attempting JWT claims verification for subject '{}'",claims.getSubject());
+        
+        for (final ClaimsValidator validator : claimValidators) {
+            log.trace("Attempting JWT claims validator '{}'", validator.getId());
+            validator.validate(claims, context);
+        }
+        log.debug("JWT claims verification for subject '{}' succeeded",claims.getSubject());
+        
+    }   
+
+}
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/ExactMatchClaimsValidator.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/ExactMatchClaimsValidator.java
new file mode 100644
index 0000000..606e1de
--- /dev/null
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/ExactMatchClaimsValidator.java
@@ -0,0 +1,111 @@
+/*
+ * 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.oidc.security.jwt.claims.impl;
+
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.oidc.jwt.claims.AbstractClaimsValidator;
+import net.shibboleth.oidc.jwt.claims.JWTValidationException;
+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;
+
+/**
+ * Validator that checks a JWT claim exactly matches (by Object equality) a value returned by a lookup strategy.
+ */
+ at ThreadSafeAfterInit
+public class ExactMatchClaimsValidator extends AbstractClaimsValidator{
+    
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(ExactMatchClaimsValidator.class);
+    
+    /** Strategy to retrieve the value to match to an ID Token claim.*/ 
+    @NonnullAfterInit private Function<ProfileRequestContext,String> valueToMatchLookupStrategy;
+    
+    /** The name of the claim to match from the ID token.*/
+    @NonnullAfterInit @NotEmpty private String claimName;
+    
+    /**
+     * Set the name of the claim to match.
+     * 
+     * @param name the claim name.
+     */
+    public void setClaimName(@Nonnull @NotEmpty final String name) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+    
+        claimName = Constraint.isNotEmpty(name, "Claim name can not be null or empty");
+    }
+    
+    /**
+     * Set the value to match lookup strategy.
+     * 
+     * @param strategy the strategy to use.
+     */
+    public void setValueToMatchLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext,String> strategy) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        
+        valueToMatchLookupStrategy = Constraint.isNotNull(strategy,
+                "Claim value to match lookup strategy can not be null");
+    }
+    
+    
+    /** {@inheritDoc} */
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+        
+        if (claimName == null) {
+            throw new ComponentInitializationException("Claim name can not be null");
+        }
+        if (valueToMatchLookupStrategy == null) {
+            throw new ComponentInitializationException("Matching value lookup strategy can not be null");
+        }
+    }
+
+    @Override
+    protected void doValidate(@Nonnull final JWTClaimsSet claims, @Nonnull final ProfileRequestContext context) 
+            throws JWTValidationException {
+        
+        final Object actualClaim = claims.getClaim(claimName);
+        if (actualClaim == null) {
+            throw new JWTValidationException("Claim '"+claimName+"' does not exist");
+        }
+        final String expectedClaim = valueToMatchLookupStrategy.apply(context);
+        log.trace("{}: Checking actual claim '{}' matches expected claim '{}'", getId(), actualClaim, expectedClaim);
+        //handle null in the yoda condition.
+        if (! actualClaim.equals(expectedClaim)) {
+            throw new JWTValidationException("JWT \"" + claimName + "\" claim has value " 
+                        + actualClaim + " but should be " + expectedClaim);
+        }
+        //all fine
+        
+    }
+
+}
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/ExpiryClaimsValidator.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/ExpiryClaimsValidator.java
new file mode 100644
index 0000000..0315893
--- /dev/null
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/ExpiryClaimsValidator.java
@@ -0,0 +1,87 @@
+/*
+ * 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.oidc.security.jwt.claims.impl;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Date;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+
+import net.shibboleth.oidc.jwt.claims.AbstractClaimsValidator;
+import net.shibboleth.oidc.jwt.claims.JWTValidationException;
+import net.shibboleth.utilities.java.support.annotation.constraint.ThreadSafeAfterInit;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/**
+ * Iff an expiration time (exp) claim is present, verifies that it is ahead of the current time, else the JWT claims set
+ * is rejected. A few minutes of {@code clockSkew} is allowed. See section 3.1.3.7 of OpenID Connect core 1.0.
+ */
+ at ThreadSafeAfterInit
+public class ExpiryClaimsValidator extends AbstractClaimsValidator {
+    
+    /** 
+     * Positive clock skew adjustment to consider when checking JWT not before and expiration 
+     * (Default value: 60 seconds). 
+     */
+    @Nonnull private Duration clockSkew;
+    
+    /** Constructor.*/
+    public ExpiryClaimsValidator() {
+        clockSkew = Duration.ofSeconds(60);
+    }
+    
+    /**
+     * Set the clock skew.
+     * 
+     * @param skew clock skew to set
+     */
+    public void setClockSkew(@Nonnull final Duration skew) {  
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        
+        clockSkew = Constraint.isNotNull(skew, "Clock skew cannot be null");
+    }
+
+    
+    /** {@inheritDoc} */
+    @Override
+    public void doValidate(@Nonnull final JWTClaimsSet claims, @Nonnull final ProfileRequestContext context) 
+            throws JWTValidationException { 
+
+        final Instant now = Instant.now();        
+
+        final Date exp = claims.getExpirationTime();
+        if (exp != null) {
+            final Instant expInstant = exp.toInstant();
+            final Instant expirationPlusSkew = expInstant.plus(clockSkew);
+                            
+            if (now.isAfter(expirationPlusSkew)) {
+                throw new JWTValidationException("Expired JWT");
+            }
+        }
+        
+    }
+
+}
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/ForcedAuthenticationActivationCondition.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/ForcedAuthenticationActivationCondition.java
new file mode 100644
index 0000000..91033d2
--- /dev/null
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/ForcedAuthenticationActivationCondition.java
@@ -0,0 +1,49 @@
+/*
+ * 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.oidc.security.jwt.claims.impl;
+
+import java.util.function.BiPredicate;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+
+/**
+ * Has forced authentication been requested? 
+ * 
+ * <p>Note, returns <b>true</b> if an authentication context is not contained in the profile request 
+ * context - enabling the validator seems a better security posture than disabling it.</p> 
+ */
+public final class ForcedAuthenticationActivationCondition implements BiPredicate<ProfileRequestContext, JWTClaimsSet> {
+
+    @Override
+    public boolean test(@Nonnull final ProfileRequestContext prc, @Nonnull final JWTClaimsSet claims) {
+        
+        final AuthenticationContext authnContext = prc.getSubcontext(AuthenticationContext.class);
+        
+        if (authnContext == null) {
+            return true;
+        }
+        return authnContext.isForceAuthn();
+    }
+    
+}
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/IDTokenClaims.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/IDTokenClaims.java
new file mode 100644
index 0000000..5a85159
--- /dev/null
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/IDTokenClaims.java
@@ -0,0 +1,66 @@
+/*
+ * 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.oidc.security.jwt.claims.impl;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.primitive.StringSupport;
+
+/** Enum of those OIDC ID token claims that are not part of the standard {@link JWTClaims JWT claims set}.*/
+public enum IDTokenClaims { 
+    
+    /** Time when the End-User authentication occurred.*/
+    AUTHENTICATION_TIME("auth_time"),
+    
+    /** String value used to associate a Client session with an ID Token, and to mitigate replay attacks.*/
+    NONCE("nonce"),
+    
+    /** Authentication Context Class Reference.*/
+    AUTHENTICATION_CONTEXT_CLASS_REFERENCE("acr"),
+    
+    /** Authentication Methods References.*/
+    AUTHENTICATION_METHODS_REFERENCES("amr"),
+    
+    /** Authorized party - the party to which the ID Token was issued.*/
+    AUTHORIZED_PARTY("azp");
+    
+    /** The registered claim name.*/
+    @Nonnull @NotEmpty private final String claimName;
+    
+    /**
+     * 
+     * Constructor.
+     *
+     * @param name the registered claim name.
+     */
+    private IDTokenClaims(@Nonnull @NotEmpty final String name) {
+        claimName = Constraint.isNotNull(StringSupport.trimOrNull(name), "Claim name can not be null or empt");
+    }
+       
+    /**
+     * Get the registered claim name.
+     * 
+     * @return the registered claim name.
+     */
+    @Nonnull public String getClaimName() {
+        return claimName;
+    }
+
+}
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/IssuedAtClaimsValidator.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/IssuedAtClaimsValidator.java
new file mode 100644
index 0000000..540db8f
--- /dev/null
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/IssuedAtClaimsValidator.java
@@ -0,0 +1,89 @@
+/*
+ * 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.oidc.security.jwt.claims.impl;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Date;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.oidc.jwt.claims.AbstractClaimsValidator;
+import net.shibboleth.oidc.jwt.claims.JWTValidationException;
+import net.shibboleth.utilities.java.support.annotation.constraint.ThreadSafeAfterInit;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/**
+ * Iff the 'iat' claim is present in the ID Token, verifies it is not to far away from 
+ * the current time. A configured window/deviation is allowed. See section 3.1.3.7 of OpenID Connect core 1.0.
+ */
+ at ThreadSafeAfterInit
+public class IssuedAtClaimsValidator extends AbstractClaimsValidator {
+    
+    /**
+     *  Maximum amount (in either direction from now) of duration for which a token is valid after 
+     *  it is issued (Default value: 60 seconds). 
+     */
+    @Nonnull private Duration iatWindow;
+    
+    /** Constructor.*/
+    public IssuedAtClaimsValidator() {
+        iatWindow = Duration.ofSeconds(60);
+    }
+    
+    /**
+     * Sets the amount of time for which a token is valid from when it was issued.
+     * 
+     * @param window amount of time for which a token is valid
+     */
+    public void setIatWindow(@Nonnull final Duration window) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        
+        Constraint.isNotNull(window, "Token issued at window cannot be null");
+        Constraint.isFalse(window.isNegative(), "Token issued at window cannot be negative");
+
+        iatWindow = window;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void doValidate(@Nonnull final JWTClaimsSet claims, 
+            @Nonnull final ProfileRequestContext context) throws JWTValidationException {
+        
+        final Date iatDate = claims.getIssueTime();
+        if (iatDate != null) {           
+            final Instant iat = iatDate.toInstant();        
+            final Instant now = Instant.now();        
+            final Duration iatDifference = Duration.between(now, iat).abs();
+            
+            if (iatWindow.compareTo(iatDifference)  < 0) {
+                throw new JWTValidationException("JWT issued-at time is too far away from the current time. "
+                        + "Token issued at '"+iat+"' was too far away from the current time '"+now+"' "
+                                + "with acceptable deviation of "
+                                + "'"+iatWindow+"', difference is '"+iatDifference+"'");
+            }
+        }
+        
+    }
+
+}
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/JWTClaims.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/JWTClaims.java
new file mode 100644
index 0000000..e82c626
--- /dev/null
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/JWTClaims.java
@@ -0,0 +1,74 @@
+/*
+ * 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.oidc.security.jwt.claims.impl;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.primitive.StringSupport;
+
+/** Enum that represents the standard set of registered JWT claims. */
+public enum JWTClaims {
+    
+    /**  The "jti" (JWT ID) claim provides a unique identifier for the JWT.*/
+    JWT_ID_CLAIM("jti"),
+    
+    /**Issuer Identifier for the Issuer of the response.*/
+    ISSUER_CLAIM("iss"),
+    
+    /** Subject Identifier. A locally unique and never reassigned identifier within the 
+     * Issuer for the End-User.*/
+    SUBJECT_CLAIM("sub"),
+    
+    /** Audience(s) that this ID Token is intended for.*/
+    AUDIENCE_CLAIM("aud"),
+    
+    /**  Expiration time on or after which the ID Token MUST NOT be accepted for processing.*/
+    EXPIRATION_TIME_CLAIM("exp"),   
+    
+    /**  Time at which the JWT was issued.*/
+    ISSUED_AT_CLAIM("iat"),
+
+    /** The "nbf" (not before) claim identifies the time before which the JWT MUST NOT be accepted for processing. */
+    NOT_BEFORE_CLAIM("nbf");
+
+    /** The IANA registered claim name.*/
+    @Nonnull @NotEmpty private final String claimName;
+
+    /**
+     * 
+     * Constructor.
+     *
+     * @param name the IANA registered claim name.
+     */
+    private JWTClaims(@Nonnull @NotEmpty final String name) {
+        claimName = Constraint.isNotNull(StringSupport.trimOrNull(name), "Claim name can not be null or empt");
+    }
+
+    /**
+     * Get the IANA registered claim name.
+     * 
+     * @return the IANA registered claim name.
+     */
+    @Nonnull
+    public String getClaimName() {
+        return claimName;
+    }
+
+}
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/NonceValidationActiviationCondition.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/NonceValidationActiviationCondition.java
new file mode 100644
index 0000000..8cc3e7a
--- /dev/null
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/NonceValidationActiviationCondition.java
@@ -0,0 +1,43 @@
+/*
+ * 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.oidc.security.jwt.claims.impl;
+
+import java.util.function.BiPredicate;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+/**
+ * Determines if nonce validation should occur based on if it exists in the JWT claims set. see section 3.1.3.7 
+ * of the OpenID Connect core 1.0 specification.
+ */
+public class NonceValidationActiviationCondition implements BiPredicate<ProfileRequestContext, JWTClaimsSet>{
+
+    @Override
+    public boolean test(@Nonnull final ProfileRequestContext context, @Nonnull final JWTClaimsSet claims) {
+
+        if (claims.getClaim(IDTokenClaims.NONCE.getClaimName()) != null) {
+            return true;
+        }
+        return false;
+    }
+
+}
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/NotBeforeClaimsValidator.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/NotBeforeClaimsValidator.java
new file mode 100644
index 0000000..45d8051
--- /dev/null
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/NotBeforeClaimsValidator.java
@@ -0,0 +1,82 @@
+/*
+ * 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.oidc.security.jwt.claims.impl;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Date;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.oidc.jwt.claims.AbstractClaimsValidator;
+import net.shibboleth.oidc.jwt.claims.JWTValidationException;
+import net.shibboleth.utilities.java.support.annotation.constraint.ThreadSafeAfterInit;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/**
+ * Iff a not-before-time (nbf) claim is present, makes sure it is before the current time. RFC7519 section 4.1.5.
+ */
+ at ThreadSafeAfterInit
+public class NotBeforeClaimsValidator extends AbstractClaimsValidator {
+    
+    /** 
+     * Positive clock skew adjustment to consider when checking JWT not before and expiration 
+     * (Default value: 60 seconds). 
+     */
+    @Nonnull private Duration clockSkew;
+    
+    /** Constructor. */
+    public NotBeforeClaimsValidator() {        
+        clockSkew = Duration.ofSeconds(60);
+    }
+    
+    /**
+     * Set the clock skew.
+     * 
+     * @param skew clock skew to set
+     */
+    public void setClockSkew(@Nonnull final Duration skew) {  
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        
+        clockSkew = Constraint.isNotNull(skew, "Clock skew cannot be null");
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void doValidate(@Nonnull final JWTClaimsSet claims, @Nonnull final ProfileRequestContext context) 
+            throws JWTValidationException {
+        
+        final Instant now = Instant.now();  
+        final Date nbf = claims.getNotBeforeTime();
+        if (nbf != null) {
+            final Instant nbfInstant = nbf.toInstant();
+            final Instant nbfInstantMinusSkew = nbfInstant.minus(clockSkew);
+            if (!nbfInstantMinusSkew.isBefore(now)) {
+                throw new JWTValidationException("JWT before use time");
+            }
+        }
+        
+    }
+
+}
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/ProhibitedClaimsValidator.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/ProhibitedClaimsValidator.java
new file mode 100644
index 0000000..b20c47e
--- /dev/null
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/ProhibitedClaimsValidator.java
@@ -0,0 +1,84 @@
+/*
+ * 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.oidc.security.jwt.claims.impl;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.oidc.jwt.claims.AbstractClaimsValidator;
+import net.shibboleth.oidc.jwt.claims.JWTValidationException;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
+import net.shibboleth.utilities.java.support.annotation.constraint.ThreadSafeAfterInit;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.primitive.StringSupport;
+
+/**
+ * Verify the claims set does not contain any of the described set of claims.
+ */
+ at ThreadSafeAfterInit
+public class ProhibitedClaimsValidator extends AbstractClaimsValidator {
+    
+    /** The names of the JWT claims that must not be present, empty set if none. */ 
+    @Nonnull @NonnullElements private Set<String> prohibitedClaims;
+    
+    /** Constructor.*/
+    public ProhibitedClaimsValidator() {
+        prohibitedClaims = Collections.emptySet();
+    }   
+    
+    /**
+     * Set the prohibited claims.
+     * 
+     * @param claims the prohibited claims.
+     */
+    public void setProhibitedClaims(@Nullable final Set<String> claims) {   
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        
+        if (claims !=null) {
+            prohibitedClaims = Set.copyOf(StringSupport.normalizeStringCollection(claims));
+        } else {
+            prohibitedClaims = Collections.emptySet();
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void doValidate(@Nonnull final JWTClaimsSet claims, @Nonnull final ProfileRequestContext context) 
+            throws JWTValidationException {
+        
+        final Set<String> presentProhibitedClaims = new HashSet<>();
+        for (final String prohibited : prohibitedClaims) {
+            if (claims.getClaims().containsKey(prohibited)) {
+                presentProhibitedClaims.add(prohibited);
+            }
+            if (!presentProhibitedClaims.isEmpty()) {
+                throw new JWTValidationException("JWT has prohibited claims: " + presentProhibitedClaims);
+            }
+        }
+        
+    }
+
+}
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/RequiredClaimsValidator.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/RequiredClaimsValidator.java
new file mode 100644
index 0000000..2ded0ef
--- /dev/null
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/RequiredClaimsValidator.java
@@ -0,0 +1,81 @@
+/*
+ * 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.oidc.security.jwt.claims.impl;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.oidc.jwt.claims.AbstractClaimsValidator;
+import net.shibboleth.oidc.jwt.claims.JWTValidationException;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
+import net.shibboleth.utilities.java.support.annotation.constraint.ThreadSafeAfterInit;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.primitive.StringSupport;
+
+/**
+ * Verify the claims set contains the given set of required claims.
+ */
+ at ThreadSafeAfterInit
+public class RequiredClaimsValidator extends AbstractClaimsValidator{
+    
+    /** The names of the JWT claims that must be present, if empty no claims are required.*/
+    @Nonnull @NonnullElements private Set<String> requiredClaims;
+    
+    /** Constructor.*/
+    public RequiredClaimsValidator() {
+        requiredClaims = Collections.emptySet();
+    }
+    
+    /**
+     * Set the required claims.
+     * 
+     * @param claims the required claims.
+     */
+    public void setRequiredClaims(@Nullable final Set<String> claims) {   
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        
+        if (claims !=null) {
+            requiredClaims = Set.copyOf(StringSupport.normalizeStringCollection(claims));
+        } else {
+            requiredClaims = Collections.emptySet();
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void doValidate(@Nonnull final JWTClaimsSet claims, @Nonnull final ProfileRequestContext context) 
+            throws JWTValidationException {
+        
+        if (!claims.getClaims().keySet().containsAll(requiredClaims)) {            
+            final Set<String> missingClaims = new HashSet<>(requiredClaims);
+            missingClaims.removeAll(claims.getClaims().keySet());
+            throw new JWTValidationException("JWT missing required claims: " + missingClaims);            
+        }
+        
+    }
+
+
+}
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/package-info.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/package-info.java
new file mode 100644
index 0000000..618b2e3
--- /dev/null
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * 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.
+ */
+
+/** Validation functions for JWT claims. */
+package net.shibboleth.oidc.security.jwt.claims.impl;
\ No newline at end of file
diff --git a/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/AbstractClaimsValidatorTest.java b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/AbstractClaimsValidatorTest.java
new file mode 100644
index 0000000..a4c708b
--- /dev/null
+++ b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/AbstractClaimsValidatorTest.java
@@ -0,0 +1,62 @@
+/* 
+ * 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.oidc.security.jwt.claims.impl;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.springframework.webflow.execution.RequestContext;
+
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.profile.context.navigate.WebflowRequestContextProfileRequestContextLookup;
+import net.shibboleth.idp.profile.testing.RequestContextBuilder;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+
+/**
+ * Abstract class for claims validator tests.
+ */
+public class AbstractClaimsValidatorTest {    
+     
+    /** The request context.*/
+    protected RequestContext src;
+
+    /** The profile request context.*/
+    protected ProfileRequestContext prc;
+    
+    /** The authentication context to add to the profile request context.*/
+    protected  AuthenticationContext ac;
+    
+    /**
+     * <p>Setup the relevant contexts per method execution.</p>
+     * 
+     * <p>Is not inherited, so must be enabled in concrete test classes, e.g.
+     * add a setup method and call super.</p>
+     * 
+     * @throws ComponentInitializationException on error.
+     */
+    public void setup() throws ComponentInitializationException {
+       
+        src = new RequestContextBuilder().buildRequestContext();
+        prc = new WebflowRequestContextProfileRequestContextLookup().apply(src);
+        ac = new AuthenticationContext();
+        
+        prc.addSubcontext(ac);
+
+    }
+    
+
+
+}
diff --git a/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/AudienceClaimsValidatorTest.java b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/AudienceClaimsValidatorTest.java
new file mode 100644
index 0000000..1246f3b
--- /dev/null
+++ b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/AudienceClaimsValidatorTest.java
@@ -0,0 +1,115 @@
+/* 
+ * 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.oidc.security.jwt.claims.impl;
+
+import java.util.Collections;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.oidc.jwt.claims.JWTValidationException;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+
+/** Test for the {@link AudienceClaimsValidator}. */
+public class AudienceClaimsValidatorTest extends AbstractClaimsValidatorTest {
+    
+    /** The validator to test.*/
+    @Nonnull private AudienceClaimsValidator validator;
+
+    
+    @BeforeMethod
+    public void setup() throws ComponentInitializationException {
+        super.setup();
+        validator = new AudienceClaimsValidator();
+    }
+    
+    @Test
+    public void doValidateTest() throws JWTValidationException, ComponentInitializationException {
+        final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().audience("client-id").build();
+        validator.setId("test-validator");
+        validator.setAudienceLookupStrategy(prc -> "client-id");
+        validator.initialize();
+        validator.validate(claimsSet, prc);
+    }
+    
+    @Test
+    public void doValidateTestMultipleAudiences() throws JWTValidationException, ComponentInitializationException {
+        final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().
+                audience(List.of("client-id","another-audience")).build();
+        validator.setId("test-validator");
+        validator.setAudienceLookupStrategy(prc -> "client-id");
+        validator.initialize();
+        validator.validate(claimsSet, prc);
+    }
+    
+    @Test(expectedExceptions = JWTValidationException.class)
+    public void doInValidateTestMultipleAudiences() throws JWTValidationException, ComponentInitializationException {
+        final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().
+                audience(List.of("wrong-client","another-audience")).build();
+        validator.setId("test-validator");
+        validator.setAudienceLookupStrategy(prc -> "client-id");
+        validator.initialize();
+        validator.validate(claimsSet, prc);
+    }
+    
+    @Test(expectedExceptions = JWTValidationException.class)
+    public void doInValidateTestEmptyAudiences() throws JWTValidationException, ComponentInitializationException {
+        final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().audience(Collections.emptyList()).build();
+        validator.setId("test-validator");
+        validator.setAudienceLookupStrategy(prc -> "client-id");
+        validator.initialize();
+        validator.validate(claimsSet, prc);
+    }
+    
+    @Test(expectedExceptions = JWTValidationException.class)
+    public void doInValidateTest() throws JWTValidationException, ComponentInitializationException {
+        final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().audience("client-id").build();
+        validator.setId("test-validator");
+        validator.setAudienceLookupStrategy(prc -> "client-id-different");
+        validator.initialize();
+        validator.validate(claimsSet, prc);
+    }
+    
+    @Test(expectedExceptions = JWTValidationException.class)
+    public void doInValidateTestNullAudienceInContext() 
+            throws JWTValidationException, ComponentInitializationException {
+        final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().audience("client-id").build();
+        validator.setId("test-validator");
+        validator.setAudienceLookupStrategy(prc -> null);
+        validator.initialize();
+        validator.validate(claimsSet, prc);
+    }
+    
+    @Test(expectedExceptions = JWTValidationException.class)
+    public void doInValidateTestNullAudienceInJWT() throws JWTValidationException, ComponentInitializationException {
+        final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().build();
+        validator.setId("test-validator");
+        validator.setAudienceLookupStrategy(prc -> "client-id");
+        validator.initialize();
+        validator.validate(claimsSet, prc);
+    }
+    
+    
+    
+    
+}
diff --git a/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/AuthenticationTimeClaimsValidatorTest.java b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/AuthenticationTimeClaimsValidatorTest.java
new file mode 100644
index 0000000..036f3fc
--- /dev/null
+++ b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/AuthenticationTimeClaimsValidatorTest.java
@@ -0,0 +1,136 @@
+/* 
+ * 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.oidc.security.jwt.claims.impl;
+
+import java.time.Duration;
+import java.time.Instant;
+
+import javax.annotation.Nonnull;
+
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.oidc.jwt.claims.JWTValidationException;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+
+/** Test for the {@link AuthenticationTimeClaimsValidator}. */
+public class AuthenticationTimeClaimsValidatorTest extends AbstractClaimsValidatorTest {
+    
+    /** The validator to test.*/
+    @Nonnull private AuthenticationTimeClaimsValidator validator;
+
+    
+    @BeforeMethod
+    public void setup() throws ComponentInitializationException {
+        super.setup();
+        validator = new AuthenticationTimeClaimsValidator();
+    }
+    
+    @Test
+    public void doValidateTest() throws JWTValidationException, ComponentInitializationException {
+        final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().claim(
+                IDTokenClaims.AUTHENTICATION_TIME.getClaimName(), Instant.now().getEpochSecond()).build();
+        validator.setId("test-validator");   
+        validator.setAuthnLifetime(Duration.ofMinutes(1));
+        validator.initialize();
+        validator.validate(claimsSet, prc);   }
+    
+   
+    
+    @Test
+    public void doValidateTestWithinLifetime() throws JWTValidationException, ComponentInitializationException {
+        final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().claim(
+                IDTokenClaims.AUTHENTICATION_TIME.getClaimName(), 
+                Instant.now().minus(Duration.ofSeconds(30)).getEpochSecond()).build();
+        validator.setId("test-validator");   
+        validator.setAuthnLifetime(Duration.ofMinutes(1));
+        validator.initialize();
+        validator.validate(claimsSet, prc);
+    }
+    
+    @Test
+    public void doValidateNoClaimButNotActive() throws JWTValidationException, ComponentInitializationException {
+        final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().build();
+        validator.setActivationCondition((prc,claims) -> false);
+        validator.setId("test-validator");   
+        validator.setAuthnLifetime(Duration.ofMinutes(1));
+        validator.initialize();
+        validator.validate(claimsSet, prc);
+    }
+    
+    @Test(expectedExceptions = JWTValidationException.class)
+    public void doInValidateTestCanNotParseDate() throws JWTValidationException, ComponentInitializationException {
+        final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().claim(
+                IDTokenClaims.AUTHENTICATION_TIME.getClaimName(),"not-a-date").build();
+        validator.setId("test-validator");   
+        validator.setAuthnLifetime(Duration.ofMinutes(1));
+        validator.initialize();
+        validator.validate(claimsSet, prc);
+    }
+    
+    @Test(expectedExceptions = JWTValidationException.class)
+    public void doInValidateTest() throws JWTValidationException, ComponentInitializationException {
+        final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().claim(
+                IDTokenClaims.AUTHENTICATION_TIME.getClaimName(), 
+                Instant.now().minus(Duration.ofMinutes(10)).getEpochSecond()).build();
+        validator.setId("test-validator");   
+        validator.setAuthnLifetime(Duration.ofMinutes(1));
+        validator.initialize();
+        validator.validate(claimsSet, prc);
+    }
+    
+    @Test(expectedExceptions = JWTValidationException.class)
+    public void doInValidateInTheFuture() throws JWTValidationException, ComponentInitializationException {
+        final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().claim(
+                IDTokenClaims.AUTHENTICATION_TIME.getClaimName(), 
+                Instant.now().plus(Duration.ofSeconds(30)).getEpochSecond()).build();
+        validator.setId("test-validator");   
+        validator.setAuthnLifetime(Duration.ofMinutes(1));
+        validator.initialize();
+        validator.validate(claimsSet, prc);
+    }
+    
+    @Test(expectedExceptions = JWTValidationException.class)
+    public void doInValidateNoClaim() throws JWTValidationException, ComponentInitializationException {
+        final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().build();
+        validator.setId("test-validator");   
+        validator.setAuthnLifetime(Duration.ofMinutes(1));
+        validator.initialize();
+        validator.validate(claimsSet, prc);
+    }
+    
+    /**
+     * Would fail, but the claim was not requested, so it is not checked.
+     * 
+     * @throws JWTValidationException on error.
+     * @throws ComponentInitializationException on error.
+     */
+    @Test
+    public void doValidateAuthTimeNotRequested() throws JWTValidationException, ComponentInitializationException {
+        final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().build();
+        validator.setId("test-validator");   
+        validator.setRequested(prc -> false);
+        validator.setAuthnLifetime(Duration.ofMinutes(1));
+        validator.initialize();
+        validator.validate(claimsSet, prc);
+    }
+    
+}
diff --git a/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/ChainingJWTClaimsValidationTest.java b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/ChainingJWTClaimsValidationTest.java
new file mode 100644
index 0000000..31f0761
--- /dev/null
+++ b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/ChainingJWTClaimsValidationTest.java
@@ -0,0 +1,267 @@
+/*
+ * 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.oidc.security.jwt.claims.impl;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+
+import javax.annotation.Nonnull;
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.oidc.jwt.claims.ClaimsValidator;
+import net.shibboleth.oidc.jwt.claims.JWTValidationException;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+
+/** Test for the {@link ChainingJWTClaimsValidation} class.*/
+public class ChainingJWTClaimsValidationTest extends AbstractClaimsValidatorTest{
+    
+    /** Validation to check.*/
+    @Nonnull private ChainingJWTClaimsValidation validation;
+    
+    /** Default set of claims.*/
+    @Nonnull private List<ClaimsValidator> validators;
+    
+    @BeforeMethod
+    public void setup() throws ComponentInitializationException {
+        super.setup();
+        validation = new ChainingJWTClaimsValidation();
+        
+        validators = new ArrayList<>(3);
+        
+        //aud validator
+        final AudienceClaimsValidator audV = new AudienceClaimsValidator();
+        audV.setAudienceLookupStrategy(prc -> "audience");
+        audV.setId("audience-check");
+        audV.initialize();
+        validators.add(audV);
+        
+        //nbf validator
+        final NotBeforeClaimsValidator nbfV = new NotBeforeClaimsValidator();
+        nbfV.setId("nbf-validator");
+        nbfV.setClockSkew(Duration.ofMinutes(1));
+        nbfV.initialize();
+        validators.add(nbfV);
+        
+        //exp validator
+        final ExpiryClaimsValidator expV = new ExpiryClaimsValidator();
+        expV.setId("exp-validator");
+        expV.setClockSkew(Duration.ofMinutes(1));
+        expV.initialize();
+        validators.add(expV);
+        
+        //iat validator
+        final IssuedAtClaimsValidator iatV = new IssuedAtClaimsValidator();
+        iatV.setId("iat-validator");
+        iatV.initialize();
+        validators.add(iatV);
+        
+        //username exact validator
+        final ExactMatchClaimsValidator usernameExactV = new ExactMatchClaimsValidator();
+        usernameExactV.setId("username-validation");
+        usernameExactV.setClaimName("username");
+        usernameExactV.setValueToMatchLookupStrategy(prc -> "jdoe");
+        usernameExactV.initialize();
+        validators.add(usernameExactV);
+        
+        //auth time
+        final AuthenticationTimeClaimsValidator authTimeV = new AuthenticationTimeClaimsValidator();
+        authTimeV.setId("auth-time-validator");
+        authTimeV.setAuthnLifetime(Duration.ofMinutes(1));
+        authTimeV.setRequested(prc -> true);
+        authTimeV.initialize();
+        validators.add(authTimeV);
+        
+        //nonce
+        final ExactMatchClaimsValidator nonceV = new ExactMatchClaimsValidator();
+        nonceV.setId("nonce-validator");
+        nonceV.setActivationCondition((prc,claims) -> true);
+        nonceV.setClaimName(IDTokenClaims.NONCE.getClaimName());
+        nonceV.setValueToMatchLookupStrategy(prc -> "nonce");
+        validators.add(nonceV);
+        
+        //required claims
+        final RequiredClaimsValidator reqV = new RequiredClaimsValidator();
+        reqV.setId("required-claims-validator");
+        reqV.setRequiredClaims(Set.of("iss","sub","aud","exp","iat"));
+        reqV.initialize();
+        validators.add(reqV);
+        
+        validation.setClaimValidators(validators);
+        validation.initialize();
+    }
+    
+    @Test(expectedExceptions = ComponentInitializationException.class)
+    public void testNoValidators() throws ComponentInitializationException {
+        final ChainingJWTClaimsValidation validationNew = new ChainingJWTClaimsValidation();
+        validationNew.initialize();
+    }
+    
+    @Test
+    public void testNullValidators() throws ComponentInitializationException {
+        final ChainingJWTClaimsValidation validationNew = new ChainingJWTClaimsValidation();
+        validationNew.setClaimValidators(null);
+        validationNew.initialize();
+    }
+    
+    @Test
+    public void testNullClaims() throws ComponentInitializationException, JWTValidationException {
+        
+        validation.validate(null, prc);
+    }
+    
+    @Test
+    public void validationSuccess() throws ComponentInitializationException, JWTValidationException {
+        
+        final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
+                .issuer("issuer")
+                .subject("jdoe")
+                .expirationTime(Date.from(Instant.now().plus(Duration.ofMinutes(10))))
+                .audience("audience")
+                .notBeforeTime(Date.from(Instant.now().minus(Duration.ofMinutes(1))))
+                .issueTime(Date.from(Instant.now()))
+                .claim(IDTokenClaims.AUTHENTICATION_TIME.getClaimName(), 
+                        Instant.now().minus(Duration.ofSeconds(30)).getEpochSecond())
+                .claim("username", "jdoe")
+                .claim(IDTokenClaims.NONCE.getClaimName(), "nonce")
+                .build();        
+
+        validation.validate(claimsSet, prc);        
+    }
+    
+    @Test(expectedExceptions = JWTValidationException.class)
+    public void validationFailedNoSubject() throws ComponentInitializationException, JWTValidationException {
+        
+        final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
+                .issuer("issuer")
+                .expirationTime(Date.from(Instant.now().plus(Duration.ofMinutes(10))))
+                .audience("audience")
+                .notBeforeTime(Date.from(Instant.now().minus(Duration.ofMinutes(1))))
+                .issueTime(Date.from(Instant.now()))
+                .claim(IDTokenClaims.AUTHENTICATION_TIME.getClaimName(), 
+                        Instant.now().minus(Duration.ofSeconds(30)).getEpochSecond())
+                .claim("username", "jdoe")
+                .claim(IDTokenClaims.NONCE.getClaimName(), "nonce")
+                .build();        
+
+        validation.validate(claimsSet, prc);        
+    }
+    
+    @Test(expectedExceptions = JWTValidationException.class)
+    public void validationFailedExpired() throws ComponentInitializationException, JWTValidationException {
+        
+        final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
+                .issuer("issuer")
+                .subject("jdoe")
+                .expirationTime(Date.from(Instant.now().minus(Duration.ofMinutes(10))))
+                .audience("audience")
+                .notBeforeTime(Date.from(Instant.now().minus(Duration.ofMinutes(1))))
+                .issueTime(Date.from(Instant.now()))
+                .claim(IDTokenClaims.AUTHENTICATION_TIME.getClaimName(), 
+                        Instant.now().minus(Duration.ofSeconds(30)).getEpochSecond())
+                .claim("username", "jdoe")
+                .claim(IDTokenClaims.NONCE.getClaimName(), "nonce")
+                .build();        
+
+        validation.validate(claimsSet, prc);        
+    }
+    
+    @Test(expectedExceptions = JWTValidationException.class)
+    public void validationFailedNotBefore() throws ComponentInitializationException, JWTValidationException {
+        
+        final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
+                .issuer("issuer")
+                .subject("jdoe")
+                .expirationTime(Date.from(Instant.now().plus(Duration.ofMinutes(10))))
+                .audience("audience")
+                .notBeforeTime(Date.from(Instant.now().plus(Duration.ofMinutes(10))))
+                .issueTime(Date.from(Instant.now()))
+                .claim(IDTokenClaims.AUTHENTICATION_TIME.getClaimName(), 
+                        Instant.now().minus(Duration.ofSeconds(30)).getEpochSecond())
+                .claim("username", "jdoe")
+                .claim(IDTokenClaims.NONCE.getClaimName(), "nonce")
+                .build();        
+
+        validation.validate(claimsSet, prc);        
+    }
+    
+    @Test(expectedExceptions = JWTValidationException.class)
+    public void validationFailedWrongNonce() throws ComponentInitializationException, JWTValidationException {
+        
+        final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
+                .issuer("issuer")
+                .subject("jdoe")
+                .expirationTime(Date.from(Instant.now().plus(Duration.ofMinutes(10))))
+                .audience("audience")
+                .notBeforeTime(Date.from(Instant.now().minus(Duration.ofMinutes(1))))
+                .issueTime(Date.from(Instant.now()))
+                .claim(IDTokenClaims.AUTHENTICATION_TIME.getClaimName(), 
+                        Instant.now().minus(Duration.ofSeconds(30)).getEpochSecond())
+                .claim("username", "jdoe")
+                .claim(IDTokenClaims.NONCE.getClaimName(), "wrong-nonce")
+                .build();        
+
+        validation.validate(claimsSet, prc);        
+    }
+    
+    @Test(expectedExceptions = JWTValidationException.class)
+    public void validationFailedWrongUsername() throws ComponentInitializationException, JWTValidationException {
+        
+        final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
+                .issuer("issuer")
+                .subject("jdoe")
+                .expirationTime(Date.from(Instant.now().plus(Duration.ofMinutes(10))))
+                .audience("audience")
+                .notBeforeTime(Date.from(Instant.now().minus(Duration.ofMinutes(1))))
+                .issueTime(Date.from(Instant.now()))
+                .claim(IDTokenClaims.AUTHENTICATION_TIME.getClaimName(), 
+                        Instant.now().minus(Duration.ofSeconds(30)).getEpochSecond())
+                .claim("username", "wrong")
+                .claim(IDTokenClaims.NONCE.getClaimName(), "nonce")
+                .build();        
+
+        validation.validate(claimsSet, prc);        
+    }
+    
+    @Test(expectedExceptions = JWTValidationException.class)
+    public void validationFailedAuthTimeTooFarInPast() throws ComponentInitializationException, JWTValidationException {
+        
+        final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
+                .issuer("issuer")
+                .subject("jdoe")
+                .expirationTime(Date.from(Instant.now().plus(Duration.ofMinutes(10))))
+                .audience("audience")
+                .notBeforeTime(Date.from(Instant.now().minus(Duration.ofMinutes(1))))
+                .issueTime(Date.from(Instant.now()))
+                .claim(IDTokenClaims.AUTHENTICATION_TIME.getClaimName(), 
+                        Instant.now().minus(Duration.ofSeconds(300)).getEpochSecond())
+                .claim("username", "jdoe")
+                .claim(IDTokenClaims.NONCE.getClaimName(), "nonce")
+                .build();        
+
+        validation.validate(claimsSet, prc);        
+    }
+
+}
diff --git a/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/ExactMatchClaimsValidatorTest.java b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/ExactMatchClaimsValidatorTest.java
new file mode 100644
index 0000000..3013972
--- /dev/null
+++ b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/ExactMatchClaimsValidatorTest.java
@@ -0,0 +1,153 @@
+/* 
+ * 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.oidc.security.jwt.claims.impl;
+
+import javax.annotation.Nonnull;
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.oidc.jwt.claims.JWTValidationException;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.logic.ConstraintViolationException;
+
+/** Test for the {@link ExactMatchClaimsValidator}. */
+public class ExactMatchClaimsValidatorTest extends AbstractClaimsValidatorTest {
+    
+    /** The validator to test.*/
+    @Nonnull private ExactMatchClaimsValidator validator;
+
+    
+    @BeforeMethod
+    public void setup() throws ComponentInitializationException {
+        super.setup();
+        validator = new ExactMatchClaimsValidator();
+    }
+    
+    @Test
+    public void doValidateTestUsername() throws JWTValidationException, ComponentInitializationException {
+        final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().claim("username", "jdoe").build();
+        validator.setId("test-validator");
+        validator.setClaimName("username");
+        validator.setValueToMatchLookupStrategy(prc -> "jdoe");
+        validator.initialize();
+        validator.validate(claimsSet, prc);
+    }
+    
+    @Test
+    public void doValidateTestNonce() throws JWTValidationException, ComponentInitializationException {
+        final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().claim(
+                IDTokenClaims.NONCE.getClaimName(), "a-nonce").build();
+        validator.setId("test-validator");
+        validator.setClaimName("nonce");
+        validator.setValueToMatchLookupStrategy(prc -> "a-nonce");
+        validator.initialize();
+        validator.validate(claimsSet, prc);
+    }
+    
+    /**
+     * Test when trying a nonce that does not exist in the claimsset.
+     * 
+     * @throws JWTValidationException on error.
+     * @throws ComponentInitializationException on error.
+     */
+    @Test(expectedExceptions = JWTValidationException.class)
+    public void doInValidateTestNonce() throws JWTValidationException, ComponentInitializationException {
+        final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().build();
+        validator.setId("test-validator");
+        validator.setClaimName("nonce");
+        validator.setValueToMatchLookupStrategy(prc -> "a-nonce");
+        validator.initialize();
+        validator.validate(claimsSet, prc);
+    }
+    
+    /**
+     * Test when trying a nonce that does not exist in the context.
+     * 
+     * @throws JWTValidationException on error.
+     * @throws ComponentInitializationException on error.
+     */
+    @Test(expectedExceptions = JWTValidationException.class)
+    public void doInValidateTestNullContextNonce() throws JWTValidationException, ComponentInitializationException {
+        final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().claim(
+                IDTokenClaims.NONCE.getClaimName(), "a-nonce").build();
+        validator.setId("test-validator");
+        validator.setClaimName("nonce");
+        validator.setValueToMatchLookupStrategy(prc ->  null);
+        validator.initialize();
+        validator.validate(claimsSet, prc);
+    }
+    
+    /**
+     * Test when trying a nonce but the validator is not active (no nonce in request).
+     * 
+     * @throws JWTValidationException on error.
+     * @throws ComponentInitializationException on error.
+     */
+    @Test
+    public void doValidateTestFalseActivationCondition() 
+            throws JWTValidationException, ComponentInitializationException {
+        final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().claim(
+                IDTokenClaims.NONCE.getClaimName(), "a-nonce").build();
+        //mimic no nonce in the context, so do not activate validator - nothing should happen.
+        validator.setActivationCondition((prc,claims) -> false);
+        validator.setId("test-validator");
+        validator.setClaimName("nonce");
+        validator.setValueToMatchLookupStrategy(prc ->  "wrong-nonce-should-not-matter");
+        validator.initialize();
+        validator.validate(claimsSet, prc);
+    }
+    
+    /**
+     * Test when trying an invalid nonce and the activator is enabled.
+     * 
+     * @throws JWTValidationException on error.
+     * @throws ComponentInitializationException on error.
+     */
+    @Test(expectedExceptions = JWTValidationException.class)
+    public void doInValidateTestTrueActivationCondition() 
+            throws JWTValidationException, ComponentInitializationException {
+        final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().claim(
+                IDTokenClaims.NONCE.getClaimName(), "a-nonce").build();
+
+        validator.setActivationCondition((prc,claims) -> true);
+        validator.setId("test-validator");
+        validator.setClaimName("nonce");
+        validator.setValueToMatchLookupStrategy(prc ->  "wrong-nonce");
+        validator.initialize();
+        validator.validate(claimsSet, prc);
+    }
+    
+    @Test(expectedExceptions = ComponentInitializationException.class)
+    public void setRequiredFieldsNotSet() throws ComponentInitializationException {
+        validator.setId("test-validator");
+        validator.initialize();
+    }
+
+    @Test(expectedExceptions = ConstraintViolationException.class)
+    public void setClaimNameTest() {
+        validator.setClaimName(null);
+    }
+
+    @Test(expectedExceptions = ConstraintViolationException.class)
+    public void setValueToMatchLookupStrategyTest() {
+       validator.setActivationCondition(null);
+    }
+}
diff --git a/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/ExpiryClaimsValidatorTest.java b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/ExpiryClaimsValidatorTest.java
new file mode 100644
index 0000000..d66e2bd
--- /dev/null
+++ b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/ExpiryClaimsValidatorTest.java
@@ -0,0 +1,92 @@
+/* 
+ * 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.oidc.security.jwt.claims.impl;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Date;
+
+import javax.annotation.Nonnull;
+
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.oidc.jwt.claims.JWTValidationException;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+
+/** Test for the {@link ExpiryClaimsValidatorTest}. */
+public class ExpiryClaimsValidatorTest extends AbstractClaimsValidatorTest {
+    
+    /** The validator to test.*/
+    @Nonnull private ExpiryClaimsValidator validator;
+
+    
+    @BeforeMethod
+    public void setup() throws ComponentInitializationException {
+        super.setup();
+        validator = new ExpiryClaimsValidator();
+    }
+    
+    @Test
+    public void doValidateTest() throws JWTValidationException, ComponentInitializationException {
+        final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().expirationTime(Date.from(Instant.now())).build();
+        validator.setId("test-validator");
+        validator.setClockSkew(Duration.ofMinutes(1));
+        validator.initialize();
+        validator.validate(claimsSet, prc);
+    }
+    
+    @Test(expectedExceptions = JWTValidationException.class)
+    public void doInValidateTest() throws JWTValidationException, ComponentInitializationException {
+        final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().expirationTime(Date.from(Instant.now()
+                .minus(Duration.ofMinutes(5))))
+                .build();
+        validator.setId("test-validator");
+        validator.setClockSkew(Duration.ofMinutes(1));
+        validator.initialize();
+        validator.validate(claimsSet, prc);
+    }
+    
+    @Test
+    public void doInValidateTestButNotActive() throws JWTValidationException, ComponentInitializationException {
+        final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().expirationTime(Date.from(Instant.now()
+                .minus(Duration.ofMinutes(5))))
+                .build();
+        validator.setActivationCondition((prc,claims) -> false);
+        validator.setId("test-validator");
+        validator.setClockSkew(Duration.ofMinutes(1));
+        validator.initialize();
+        validator.validate(claimsSet, prc);
+    }
+    
+    @Test
+    public void doInValidateTestNoExp() throws JWTValidationException, ComponentInitializationException {
+        final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().build();
+        validator.setId("test-validator");
+        validator.setClockSkew(Duration.ofMinutes(1));
+        validator.initialize();
+        validator.validate(claimsSet, prc);
+    }
+    
+      
+    
+    
+}
diff --git a/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/IssuedAtClaimsValidatorTest.java b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/IssuedAtClaimsValidatorTest.java
new file mode 100644
index 0000000..a450b82
--- /dev/null
+++ b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/IssuedAtClaimsValidatorTest.java
@@ -0,0 +1,99 @@
+/* 
+ * 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.oidc.security.jwt.claims.impl;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Date;
+
+import javax.annotation.Nonnull;
+
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.oidc.jwt.claims.JWTValidationException;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+
+/** Test for the {@link IssuedAtClaimsValidator}. */
+public class IssuedAtClaimsValidatorTest extends AbstractClaimsValidatorTest {
+    
+    /** The validator to test.*/
+    @Nonnull private IssuedAtClaimsValidator validator;
+
+    
+    @BeforeMethod
+    public void setup() throws ComponentInitializationException {
+        super.setup();
+        validator = new IssuedAtClaimsValidator();
+    }
+    
+    @Test
+    public void doValidateTest() throws JWTValidationException, ComponentInitializationException {
+        final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().issueTime(Date.from(Instant.now())).build();
+        validator.setId("test-validator");
+        validator.setIatWindow(Duration.ofMinutes(1));
+        validator.initialize();
+        validator.validate(claimsSet, prc);
+    }
+    
+    @Test
+    public void doValidateTestIssuedInPastButInWindow() 
+            throws JWTValidationException, ComponentInitializationException {
+        final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().issueTime(
+                Date.from(Instant.now().minus(Duration.ofMinutes(10)))).build();
+        validator.setId("test-validator");
+        validator.setIatWindow(Duration.ofMinutes(20));
+        validator.initialize();
+        validator.validate(claimsSet, prc);
+    }
+    
+    @Test
+    public void doValidateTestNoIssuedAtClaim() throws JWTValidationException, ComponentInitializationException {
+        final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().build();
+        validator.setId("test-validator");
+        validator.setIatWindow(Duration.ofMinutes(1));
+        validator.initialize();
+        validator.validate(claimsSet, prc);
+    }
+    
+    @Test(expectedExceptions = JWTValidationException.class)
+    public void doInValidateTestIssuedTooFarInPast() throws JWTValidationException, ComponentInitializationException {
+        final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().issueTime(
+                Date.from(Instant.now().minus(Duration.ofMinutes(10)))).build();
+        validator.setId("test-validator");
+        validator.setIatWindow(Duration.ofMinutes(1));
+        validator.initialize();
+        validator.validate(claimsSet, prc);
+    }
+    
+    @Test(expectedExceptions = JWTValidationException.class)
+    public void doInValidateTestIssuedTooFarInFuture() throws JWTValidationException, ComponentInitializationException {
+        final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().issueTime(
+                Date.from(Instant.now().plus(Duration.ofMinutes(10)))).build();
+        validator.setId("test-validator");
+        validator.setIatWindow(Duration.ofMinutes(1));
+        validator.initialize();
+        validator.validate(claimsSet, prc);
+    }
+    
+    
+     
+}
diff --git a/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/NotBeforeClaimsValidatorTest.java b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/NotBeforeClaimsValidatorTest.java
new file mode 100644
index 0000000..70f1245
--- /dev/null
+++ b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/NotBeforeClaimsValidatorTest.java
@@ -0,0 +1,99 @@
+/* 
+ * 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.oidc.security.jwt.claims.impl;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Date;
+
+import javax.annotation.Nonnull;
+
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.oidc.jwt.claims.JWTValidationException;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+
+/** Test for the {@link NotBeforeClaimsValidator}. */
+public class NotBeforeClaimsValidatorTest extends AbstractClaimsValidatorTest {
+    
+    /** The validator to test.*/
+    @Nonnull private NotBeforeClaimsValidator validator;
+
+    
+    @BeforeMethod
+    public void setup() throws ComponentInitializationException {
+        super.setup();
+        validator = new NotBeforeClaimsValidator();
+    }
+    
+    @Test
+    public void doValidateTest() throws JWTValidationException, ComponentInitializationException {
+        final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().notBeforeTime(Date.from(Instant.now())).build();
+        validator.setId("test-validator");
+        validator.setClockSkew(Duration.ofMinutes(1));
+        validator.initialize();
+        validator.validate(claimsSet, prc);
+    }
+    
+    @Test
+    public void doValidateTestInFutureInsideWindow() throws JWTValidationException, ComponentInitializationException {
+        final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().notBeforeTime(Date.from(Instant.now()
+                .plus(Duration.ofMinutes(1)))).build();
+        validator.setId("test-validator");
+        validator.setClockSkew(Duration.ofMinutes(2));
+        validator.initialize();
+        validator.validate(claimsSet, prc);
+    }
+    
+    @Test
+    public void doValidateTestInPast() throws JWTValidationException, ComponentInitializationException {
+        final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().notBeforeTime(Date.from(Instant.now()
+                .minus(Duration.ofMinutes(10)))).build();
+        validator.setId("test-validator");
+        validator.setClockSkew(Duration.ofMinutes(1));
+        validator.initialize();
+        validator.validate(claimsSet, prc);
+    }
+    
+    @Test
+    public void doValidateTestNoNBFTime() throws JWTValidationException, ComponentInitializationException {
+        final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().build();
+        validator.setId("test-validator");
+        validator.setClockSkew(Duration.ofMinutes(1));
+        validator.initialize();
+        validator.validate(claimsSet, prc);
+    }
+    
+    @Test(expectedExceptions = JWTValidationException.class)
+    public void doInValidateTestInFuture() throws JWTValidationException, ComponentInitializationException {
+        final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().notBeforeTime(Date.from(Instant.now()
+                .plus(Duration.ofMinutes(10)))).build();
+        validator.setId("test-validator");
+        validator.setClockSkew(Duration.ofMinutes(1));
+        validator.initialize();
+        validator.validate(claimsSet, prc);
+    }
+    
+   
+    
+     
+}
diff --git a/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/ProhibitedClaimsValidatorTest.java b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/ProhibitedClaimsValidatorTest.java
new file mode 100644
index 0000000..a028a9d
--- /dev/null
+++ b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/ProhibitedClaimsValidatorTest.java
@@ -0,0 +1,84 @@
+/* 
+ * 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.oidc.security.jwt.claims.impl;
+
+import java.util.Set;
+
+import javax.annotation.Nonnull;
+
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.oidc.jwt.claims.JWTValidationException;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+
+/** Test for the {@link ProhibitedClaimsValidator}. */
+public class ProhibitedClaimsValidatorTest extends AbstractClaimsValidatorTest {
+    
+    /** The validator to test.*/
+    @Nonnull private ProhibitedClaimsValidator validator;
+
+    
+    @BeforeMethod
+    public void setup() throws ComponentInitializationException {
+        super.setup();
+        validator = new ProhibitedClaimsValidator();
+    }
+    
+    @Test
+    public void doValidateTest() throws JWTValidationException, ComponentInitializationException {
+        final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().claim("not-prohibited", "whatever").build();
+        validator.setId("test-validator");
+        validator.setProhibitedClaims(Set.of("prohibited"));
+        validator.initialize();
+        validator.validate(claimsSet, prc);
+    }
+    
+    @Test(expectedExceptions = JWTValidationException.class)
+    public void doInValidateTest() throws JWTValidationException, ComponentInitializationException {
+        final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().claim("prohibited", "whatever").build();
+        validator.setId("test-validator");
+        validator.setProhibitedClaims(Set.of("prohibited"));
+        validator.initialize();
+        validator.validate(claimsSet, prc);
+    }
+
+    
+    @Test
+    public void doValidateTestNoProhibitedClaims() throws JWTValidationException, ComponentInitializationException {
+        final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().claim("not-prohibited", "whatever").build();
+        validator.setId("test-validator");
+        validator.initialize();
+        validator.validate(claimsSet, prc);
+    }
+    
+    @Test
+    public void doValidateTestSetNullProhibitedClaims() 
+            throws JWTValidationException, ComponentInitializationException {
+        final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().claim("not-prohibited", "whatever").build();
+        validator.setId("test-validator");
+        validator.setProhibitedClaims(null);
+        validator.initialize();
+        validator.validate(claimsSet, prc);
+    }
+     
+     
+}
diff --git a/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/RequiredClaimsValidatorTest.java b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/RequiredClaimsValidatorTest.java
new file mode 100644
index 0000000..8fa627c
--- /dev/null
+++ b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/RequiredClaimsValidatorTest.java
@@ -0,0 +1,83 @@
+/* 
+ * 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.oidc.security.jwt.claims.impl;
+
+import java.util.Set;
+
+import javax.annotation.Nonnull;
+
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.oidc.jwt.claims.JWTValidationException;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+
+/** Test for the {@link RequiredClaimsValidator}. */
+public class RequiredClaimsValidatorTest extends AbstractClaimsValidatorTest {
+    
+    /** The validator to test.*/
+    @Nonnull private RequiredClaimsValidator validator;
+
+    
+    @BeforeMethod
+    public void setup() throws ComponentInitializationException {
+        super.setup();
+        validator = new RequiredClaimsValidator();
+    }
+    
+    @Test
+    public void doValidateTest() throws JWTValidationException, ComponentInitializationException {
+        final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().claim("required", "whatever").build();
+        validator.setId("test-validator");
+        validator.setRequiredClaims(Set.of("required"));
+        validator.initialize();
+        validator.validate(claimsSet, prc);
+    }
+    
+    @Test(expectedExceptions = JWTValidationException.class)
+    public void doInValidateTest() throws JWTValidationException, ComponentInitializationException {
+        final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().claim("no-required", "whatever").build();
+        validator.setId("test-validator");
+        validator.setRequiredClaims(Set.of("required"));
+        validator.initialize();
+        validator.validate(claimsSet, prc);
+    }
+
+    
+    @Test
+    public void doValidateTestNoRquiredClaims() throws JWTValidationException, ComponentInitializationException {
+        final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().claim("not-required", "whatever").build();
+        validator.setId("test-validator");
+        validator.initialize();
+        validator.validate(claimsSet, prc);
+    }
+    
+    @Test
+    public void doValidateTestSetNullRequiredClaims() throws JWTValidationException, ComponentInitializationException {
+        final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().claim("not-required", "whatever").build();
+        validator.setId("test-validator");
+        validator.setRequiredClaims(null);
+        validator.initialize();
+        validator.validate(claimsSet, prc);
+    }
+     
+     
+}
diff --git a/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/package-info.java b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/package-info.java
new file mode 100644
index 0000000..0752fb5
--- /dev/null
+++ b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * 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.
+ */
+
+/** Validation function tests for JWT claims. */
+package net.shibboleth.oidc.security.jwt.claims.impl;
\ No newline at end of file
diff --git a/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/impl/BaseStorageServiceClientInformationComponent.java b/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/impl/BaseStorageServiceClientInformationComponent.java
index 9bac215..f5c8402 100644
--- a/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/impl/BaseStorageServiceClientInformationComponent.java
+++ b/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/impl/BaseStorageServiceClientInformationComponent.java
@@ -21,6 +21,8 @@ import javax.annotation.Nonnull;
 
 import org.opensaml.storage.StorageService;
 
+import net.shibboleth.oidc.metadata.ClientInformationManager;
+import net.shibboleth.oidc.metadata.ClientInformationResolver;
 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.AbstractIdentifiableInitializableComponent;

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


More information about the commits mailing list