[java-idp-oidc] branch main updated: JCOMOIDC-41 - Move OIDC Signature Validation resolvers and parameter classes to commons

Henri Mikkonen henri.mikkonen at iki.fi
Wed Mar 1 14:56:02 UTC 2023


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

hjmikkon pushed a commit to branch main
in repository java-idp-oidc.

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

The following commit(s) were added to refs/heads/main by this push:
     new e7dfec5e JCOMOIDC-41 - Move OIDC Signature Validation resolvers and parameter classes to commons
e7dfec5e is described below

commit e7dfec5ec309825132240fb9ddf8e32b7ad2db6a
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Wed Mar 1 16:54:31 2023 +0200

    JCOMOIDC-41 - Move OIDC Signature Validation resolvers and parameter classes to commons
    
    https://shibboleth.atlassian.net/browse/JCOMOIDC-41
    
    Improved the signature signing configuration wiring in authorize, token and userinfo flows.
    Improved flow tests for verifying that the issued JWT signature matches the expected configuration.
---
 .../idp/flows/oidc/authorize/authorize-beans.xml   |  24 +-
 .../idp/flows/oidc/token/token-beans.xml           |  57 ++-
 .../idp/flows/oidc/userinfo/userinfo-beans.xml     |  11 +-
 .../op/profile/flow/AbstractOidcApiFlowTest.java   |  32 --
 .../AbstractOidcClientAuthenticationFlowTest.java  |  14 +-
 .../oidc/op/profile/flow/AbstractOidcFlowTest.java | 165 ++++++---
 .../oidc/op/profile/flow/AuthorizeFlowTest.java    |  19 +
 .../op/profile/flow/IssuedJWTSignatureTest.java    | 404 +++++++++++++++++++++
 .../plugin/oidc/op/profile/flow/TokenFlowTest.java |  62 +++-
 .../plugin/oidc/op/profile/flow/UserInfoTest.java  |  15 +-
 .../src/test/resources/conf/relying-party.xml      |  10 +-
 11 files changed, 703 insertions(+), 110 deletions(-)

diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml
index 59816c26..d94d50eb 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml
@@ -508,7 +508,7 @@
 
     <bean id="PopulateThirdPartyAccessTokenSignatureSigningParameters"
         class="net.shibboleth.oidc.profile.impl.PopulateJWTSignatureSigningParameters"
-        scope="prototype"
+        scope="prototype" p:noResultIsError="true"
         p:securityParametersContextLookupStrategy-ref="AudienceSecurityParametersCreationStrategy"
         c:strategy-ref="shibboleth.MessageContextLookup.Outbound">
         <property name="configurationLookupStrategy">
@@ -517,7 +517,14 @@
                 p:relyingPartyConfigurationResolver-ref="shibboleth.RelyingPartyConfigurationResolver" />
         </property>
         <property name="signatureSigningParametersResolver">
-            <bean class="net.shibboleth.oidc.security.jose.impl.BasicSignatureSigningParametersResolver" />
+            <bean class="net.shibboleth.oidc.security.jose.impl.ClientInformationSignatureSigningParametersResolver">
+                <constructor-arg name="signatureAlgorithmLookupStrategy">
+                    <bean
+                        class="net.shibboleth.oidc.profile.config.navigate.ClientInformationStringValueLookupFunction"
+                        c:keyName="id_token_signed_response_alg" />
+                </constructor-arg>
+                <constructor-arg name="defaultAlgorithmValue" value="RS256" />
+            </bean>
         </property>
     </bean>
         
@@ -564,7 +571,7 @@
         p:typeHeader="at+jwt" />
         -->
     <bean id="SignAccessToken" class="net.shibboleth.idp.profile.impl.WebFlowMessageHandlerAdaptor"
-            scope="prototype" c:executionDirection="OUTBOUND ">
+            scope="prototype" c:executionDirection="OUTBOUND">
         <constructor-arg name="messageHandler">
             <bean id="SignAccessTokenHandler"
                 class="net.shibboleth.oidc.security.impl.SignJWTHandler" scope="prototype" p:logName="Access Token"
@@ -585,14 +592,21 @@
 
     <bean id="PopulateIDTokenSignatureSigningParameters"
         class="net.shibboleth.oidc.profile.impl.PopulateJWTSignatureSigningParameters" scope="prototype"
-        c:strategy-ref="shibboleth.MessageContextLookup.Outbound">
+        c:strategy-ref="shibboleth.MessageContextLookup.Inbound" p:noResultIsError="true">
         <property name="configurationLookupStrategy">
             <bean lazy-init="true"
                 class="net.shibboleth.oidc.profile.config.navigate.JWTSignatureSigningConfigurationLookupFunction"
                 p:relyingPartyConfigurationResolver-ref="shibboleth.RelyingPartyConfigurationResolver" />
         </property>
         <property name="signatureSigningParametersResolver">
-            <bean class="net.shibboleth.oidc.security.jose.impl.BasicSignatureSigningParametersResolver" />
+            <bean class="net.shibboleth.oidc.security.jose.impl.ClientInformationSignatureSigningParametersResolver">
+                <constructor-arg name="signatureAlgorithmLookupStrategy">
+                    <bean
+                        class="net.shibboleth.oidc.profile.config.navigate.ClientInformationStringValueLookupFunction"
+                        c:keyName="id_token_signed_response_alg" />
+                </constructor-arg>
+                <constructor-arg name="defaultAlgorithmValue" value="RS256" />
+            </bean>
         </property>
     </bean>
 
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-beans.xml
index 11b5e3c3..579f0957 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-beans.xml
@@ -309,7 +309,14 @@
                 p:relyingPartyConfigurationResolver-ref="shibboleth.RelyingPartyConfigurationResolver" />
         </property>
         <property name="signatureSigningParametersResolver">
-            <bean class="net.shibboleth.oidc.security.jose.impl.BasicSignatureSigningParametersResolver" />
+            <bean class="net.shibboleth.oidc.security.jose.impl.ClientInformationSignatureSigningParametersResolver">
+                <constructor-arg name="signatureAlgorithmLookupStrategy">
+                    <bean
+                        class="net.shibboleth.oidc.profile.config.navigate.ClientInformationStringValueLookupFunction"
+                        c:keyName="id_token_signed_response_alg" />
+                </constructor-arg>
+                <constructor-arg name="defaultAlgorithmValue" value="RS256" />
+            </bean>
         </property>
         <property name="securityParametersContextLookupStrategy">
             <bean parent="shibboleth.Functions.Compose"
@@ -323,11 +330,23 @@
         p:dataSealer="#{getObject('%{idp.oidc.tokenSealer:shibboleth.oidc.TokenSealer}'.trim())}" />
 
     <bean id="SignOIDCAccessToken" class="net.shibboleth.idp.profile.impl.WebFlowMessageHandlerAdaptor"
-            scope="prototype" c:executionDirection="OUTBOUND ">
+            scope="prototype" c:executionDirection="OUTBOUND">
         <constructor-arg name="messageHandler">
             <bean id="SignOIDCAccessTokenHandler"
                 class="net.shibboleth.oidc.security.impl.SignJWTHandler" scope="prototype" p:logName="Access Token"
                 p:typeHeader="at+jwt">
+                <property name="securityParametersLookupStrategy">
+                    <bean parent="shibboleth.Functions.Compose">
+                        <constructor-arg name="g">
+                            <bean parent="shibboleth.Functions.Compose"
+                                c:g-ref="shibboleth.oidc.ChildLookupOrCreate.JWTSecurityParameters"
+                                c:f-ref="shibboleth.ChildLookup.RelyingParty" />
+                        </constructor-arg>
+                        <constructor-arg name="f">
+                            <bean parent="shibboleth.Functions.Expression" c:expression="#input.getParent()" />
+                        </constructor-arg>
+                    </bean>
+                </property>
                 <property name="claimsToSignLookupStrategy">
                      <bean
                         class="net.shibboleth.idp.plugin.oidc.op.messaging.context.logic.JWTClaimsSetFromJWTAccessTokenLookupFunction" />
@@ -370,15 +389,22 @@
 
     <bean id="PopulateIDTokenSignatureSigningParameters"
             class="net.shibboleth.oidc.profile.impl.PopulateJWTSignatureSigningParameters"
-            scope="prototype"
-            c:strategy-ref="shibboleth.MessageContextLookup.Outbound">
+            scope="prototype" p:noResultIsError="true"
+            c:strategy-ref="shibboleth.MessageContextLookup.Inbound">
         <property name="configurationLookupStrategy">
             <bean lazy-init="true"
                 class="net.shibboleth.oidc.profile.config.navigate.JWTSignatureSigningConfigurationLookupFunction"
                 p:relyingPartyConfigurationResolver-ref="shibboleth.RelyingPartyConfigurationResolver" />
         </property>
         <property name="signatureSigningParametersResolver">
-            <bean class="net.shibboleth.oidc.security.jose.impl.BasicSignatureSigningParametersResolver" />
+            <bean class="net.shibboleth.oidc.security.jose.impl.ClientInformationSignatureSigningParametersResolver">
+                <constructor-arg name="signatureAlgorithmLookupStrategy">
+                    <bean
+                        class="net.shibboleth.oidc.profile.config.navigate.ClientInformationStringValueLookupFunction"
+                        c:keyName="id_token_signed_response_alg" />
+                </constructor-arg>
+                <constructor-arg name="defaultAlgorithmValue" value="RS256" />
+            </bean>
         </property>
          <property name="securityParametersContextLookupStrategy">
             <bean parent="shibboleth.Functions.Compose"
@@ -462,7 +488,7 @@
         class="net.shibboleth.idp.plugin.oidc.op.profile.impl.ManipulateClaimsForIDToken" scope="prototype" />
 
     <bean id="SignIDToken" class="net.shibboleth.idp.profile.impl.WebFlowMessageHandlerAdaptor"
-            scope="prototype" c:executionDirection="OUTBOUND ">
+            scope="prototype" c:executionDirection="OUTBOUND">
         <constructor-arg name="messageHandler">
             <bean id="SignIDTokenHandler"
                 class="net.shibboleth.oidc.security.impl.SignJWTHandler" scope="prototype"  p:logName="ID Token">
@@ -512,16 +538,23 @@
 
     <bean id="PopulateThirdPartyAccessTokenSignatureSigningParameters"
         class="net.shibboleth.oidc.profile.impl.PopulateJWTSignatureSigningParameters"
-        scope="prototype"
-        c:strategy-ref="shibboleth.MessageContextLookup.Outbound"
-        p:securityParametersContextLookupStrategy-ref="AudienceSecurityParametersCreationStrategy">
+        scope="prototype" p:noResultIsError="true"
+        p:securityParametersContextLookupStrategy-ref="AudienceSecurityParametersCreationStrategy"
+        c:strategy-ref="shibboleth.MessageContextLookup.Outbound">
         <property name="configurationLookupStrategy">
             <bean lazy-init="true"
                 class="net.shibboleth.oidc.profile.config.navigate.JWTSignatureSigningConfigurationLookupFunction"
-                p:relyingPartyConfigurationResolver-ref="shibboleth.RelyingPartyConfigurationResolver" />
+                p:relyingPartyConfigurationResolver-ref="shibboleth.RelyingPartyConfigurationResolver"/>
         </property>
         <property name="signatureSigningParametersResolver">
-            <bean class="net.shibboleth.oidc.security.jose.impl.BasicSignatureSigningParametersResolver" />
+            <bean class="net.shibboleth.oidc.security.jose.impl.ClientInformationSignatureSigningParametersResolver">
+                <constructor-arg name="signatureAlgorithmLookupStrategy">
+                    <bean
+                        class="net.shibboleth.oidc.profile.config.navigate.ClientInformationStringValueLookupFunction"
+                        c:keyName="id_token_signed_response_alg" />
+                </constructor-arg>
+                <constructor-arg name="defaultAlgorithmValue" value="RS256" />
+            </bean>
         </property>
     </bean>        
 
@@ -611,7 +644,7 @@
         p:relyingPartyContextLookupStrategy-ref="AudienceRelyingPartyCreationStrategy" />
 
     <bean id="SignAccessToken" class="net.shibboleth.idp.profile.impl.WebFlowMessageHandlerAdaptor"
-            scope="prototype" c:executionDirection="OUTBOUND ">
+            scope="prototype" c:executionDirection="OUTBOUND">
         <constructor-arg name="messageHandler">
             <bean id="SignAccessTokenHandler"
                 class="net.shibboleth.oidc.security.impl.SignJWTHandler" scope="prototype" p:logName="Access Token"
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/userinfo/userinfo-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/userinfo/userinfo-beans.xml
index 129653a4..5b4ac97c 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/userinfo/userinfo-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/userinfo/userinfo-beans.xml
@@ -79,7 +79,7 @@
 
     <bean id="PopulateUserInfoResponseSignatureSigningParameters"
         class="net.shibboleth.oidc.profile.impl.PopulateJWTSignatureSigningParameters" scope="prototype"
-        c:strategy-ref="shibboleth.MessageContextLookup.Outbound"
+        c:strategy-ref="shibboleth.MessageContextLookup.Inbound"
         p:configurationLookupStrategy-ref="JWTSignatureSigningConfigurationLookupFunction"
         p:signatureSigningParametersResolver-ref="shibboleth.oidc.UserInfoSignatureSigningParametersResolver">
         <property name="securityParametersContextLookupStrategy">
@@ -103,7 +103,14 @@
         
 
     <bean id="shibboleth.oidc.UserInfoSignatureSigningParametersResolver"
-        class="net.shibboleth.oidc.security.jose.impl.BasicSignatureSigningParametersResolver" />
+        class="net.shibboleth.oidc.security.jose.impl.ClientInformationSignatureSigningParametersResolver">
+        <constructor-arg name="signatureAlgorithmLookupStrategy">
+            <bean
+                class="net.shibboleth.oidc.profile.config.navigate.ClientInformationStringValueLookupFunction"
+                c:keyName="userinfo_signed_response_alg" />
+        </constructor-arg>
+        <constructor-arg name="defaultAlgorithmValue" value="" />
+     </bean>
 
     <bean id="PopulateUserInfoResponseEncryptionParameters"
         class="net.shibboleth.oidc.profile.impl.PopulateJWTEncryptionParameters" scope="prototype"
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AbstractOidcApiFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AbstractOidcApiFlowTest.java
index 1b09f786..268f4f6a 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AbstractOidcApiFlowTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AbstractOidcApiFlowTest.java
@@ -55,11 +55,6 @@ public class AbstractOidcApiFlowTest extends AbstractOidcFlowTest {
     protected AbstractOidcApiFlowTest(final String flowId) {
         super(flowId);
     }
-    
-    protected BearerAccessToken buildToken(final String clientId, final String subject, final Scope scope)
-            throws URISyntaxException, NoSuchAlgorithmException, DataSealerException, ComponentInitializationException {
-        return buildToken(clientId, subject, scope, null);
-    }
 
     protected BearerAccessToken buildLegacyToken(final String clientId, final String subject, final Scope scope,
             String... consentedClaims)
@@ -67,33 +62,6 @@ public class AbstractOidcApiFlowTest extends AbstractOidcFlowTest {
         return buildLegacyToken(clientId, subject, scope, null, consentedClaims);
     }
 
-    protected BearerAccessToken buildToken(final String clientId, final String subject, final Scope scope,
-            final ClaimsSet userInfoDeliverySet)
-            throws URISyntaxException, NoSuchAlgorithmException, DataSealerException, ComponentInitializationException {
-        return buildToken(clientId, subject, scope, userInfoDeliverySet, null, null);
-    }
-
-    protected BearerAccessToken buildToken(final String clientId, final String subject, final Scope scope,
-            final ClaimsSet userInfoDeliverySet, final String id, final String rootId)
-            throws URISyntaxException, NoSuchAlgorithmException, DataSealerException, ComponentInitializationException {
-        final String jti = id == null ? idGenerator.generateIdentifier() : id;
-        final TokenClaimsSet claims = new AccessTokenClaimsSet.Builder()
-                .setJWTID(jti)
-                .setClientID(new ClientID(clientId))
-                .setIssuer("https://op.example.org")
-                .setPrincipal("jdoe")
-                .setSubject(subject)
-                .setIssuedAt(Instant.now())
-                .setExpiresAt(Instant.now().plusSeconds(30))
-                .setAuthenticationTime(Instant.now())
-                .setRedirectURI(new URI("https://example.org/cb"))
-                .setScope(scope)
-                .setDlClaimsUI(userInfoDeliverySet)
-                .setRootTokenIdentifier(rootId)
-                .build();
-        return new BearerAccessToken(claims.serialize(BaseOIDCResponseActionTest.initializeDataSealer()));
-    }
-
     protected RefreshToken buildRefreshToken(final String clientId, final String subject, final Scope scope,
             final ClaimsSet userInfoDeliverySet, final String id, final String rootId)
             throws URISyntaxException, NoSuchAlgorithmException, DataSealerException, ComponentInitializationException {
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AbstractOidcClientAuthenticationFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AbstractOidcClientAuthenticationFlowTest.java
index 305c6fd5..f71f2aeb 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AbstractOidcClientAuthenticationFlowTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AbstractOidcClientAuthenticationFlowTest.java
@@ -601,16 +601,24 @@ public abstract class AbstractOidcClientAuthenticationFlowTest extends AbstractO
     }
 
     protected ClientSecretJWT buildSecretJwtAuth(String secret) throws JOSEException, URISyntaxException {
-        return new ClientSecretJWT(new ClientID(clientId), new URI(jwtAud),
+        return buildSecretJwtAuth(clientId, secret);
+    }
+
+    protected ClientSecretJWT buildSecretJwtAuth(final String id, String secret) throws JOSEException, URISyntaxException {
+        return buildSecretJwtAuth(id, secret, jwtAud);
+    }
+
+    protected static ClientSecretJWT buildSecretJwtAuth(final String id, String secret, final String jwtAud) throws JOSEException, URISyntaxException {
+        return new ClientSecretJWT(new ClientID(id), new URI(jwtAud),
                 JWSAlgorithm.HS256, new Secret(secret));
     }
-    
+
     protected PrivateKeyJWT buildPrivateKeyJwtAuth() throws JOSEException, URISyntaxException {
         return new PrivateKeyJWT(new ClientID(clientId), new URI(jwtAud),
                 JWSAlgorithm.RS256, (PrivateKey) rsaPrivateKey, null, null);   
     }
     
-    protected void populateClientAssertionParams(final Map<String, String> requestParameters, 
+    protected static void populateClientAssertionParams(final Map<String, String> requestParameters, 
             final JWTAuthentication clientAuth) {
         requestParameters.put("client_assertion", clientAuth.getClientAssertion().serialize());
         requestParameters.put("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer");
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AbstractOidcFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AbstractOidcFlowTest.java
index 9ce472df..3de58353 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AbstractOidcFlowTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AbstractOidcFlowTest.java
@@ -80,15 +80,22 @@ import com.nimbusds.oauth2.sdk.Scope;
 import com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod;
 import com.nimbusds.oauth2.sdk.auth.Secret;
 import com.nimbusds.oauth2.sdk.id.ClientID;
+import com.nimbusds.oauth2.sdk.token.BearerAccessToken;
+import com.nimbusds.openid.connect.sdk.claims.ClaimsSet;
 import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
 import com.nimbusds.openid.connect.sdk.rp.OIDCClientMetadata;
 
+import net.shibboleth.idp.plugin.oidc.op.profile.impl.BaseOIDCResponseActionTest;
+import net.shibboleth.idp.plugin.oidc.op.token.support.AccessTokenClaimsSet;
+import net.shibboleth.idp.plugin.oidc.op.token.support.TokenClaimsSet;
 import net.shibboleth.idp.test.flows.AbstractFlowTest;
 import net.shibboleth.oidc.metadata.impl.BaseStorageServiceClientInformationComponent;
 import net.shibboleth.oidc.security.credential.BasicJWKCredential;
 import net.shibboleth.oidc.security.credential.impl.BasicJWKCredentialFactoryBean;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
 import net.shibboleth.utilities.java.support.net.HttpServletRequestResponseContext;
 import net.shibboleth.utilities.java.support.security.DataSealer;
+import net.shibboleth.utilities.java.support.security.DataSealerException;
 
 /**
  * Abstract unit test for the OIDC flows.
@@ -98,14 +105,18 @@ public abstract class AbstractOidcFlowTest extends AbstractFlowTest {
     
     public static final String END_STATE_ID = "CommitResponse";
     
-    private String flowId;
+    protected String flowId;
     
-    private String endStateId;
+    protected String endStateId;
     
     @Autowired
     @Qualifier("shibboleth.oidc.TokenSealer")
     private DataSealer dataSealer;
 
+    @Autowired
+    @Qualifier("shibboleth.StorageService")
+    StorageService storageService;
+
     RSAPrivateKey rsaPrivateKey;
     RSAPublicKey rsaPublicKey;
     
@@ -213,10 +224,14 @@ public abstract class AbstractOidcFlowTest extends AbstractFlowTest {
     }
     
     protected void setHttpFormRequest(final String method, final Map<String, String> parameters) {
-        setRequest(method, "", "application/x-www-form-urlencoded");
+        setHttpFormRequest(request, method, parameters);
+    }
+
+    protected static void setHttpFormRequest(final MockHttpServletRequest request, final String method, final Map<String, String> parameters) {
+        setRequest(request, method, "", "application/x-www-form-urlencoded");
         request.setParameters(parameters);
     }
-    
+
     protected void setBasicAuth(final String username, final String password) {
         request.removeHeader("Authorization");
         request.addHeader("Authorization",
@@ -224,11 +239,15 @@ public abstract class AbstractOidcFlowTest extends AbstractFlowTest {
     }
     
     protected void setRequest(final String method, final String body, final String contentType) {
+        setRequest(request, method, body, contentType);
+    }
+
+    protected static void setRequest(final MockHttpServletRequest request, final String method, final String body, final String contentType) {
         request.setMethod(method);
         request.setContentType(contentType);
         request.setContent(body.getBytes());   
     }
-    
+
     protected void storeMetadata(final StorageService storageService, final String clientId, final String secret,
             final Scope scope, final String... redirectUri) throws IOException {
         storeMetadata(storageService, clientId, secret, scope, null, ClientAuthenticationMethod.CLIENT_SECRET_BASIC,
@@ -317,9 +336,7 @@ public abstract class AbstractOidcFlowTest extends AbstractFlowTest {
             final EncryptionMethod requestObjectEncMethod, final String... redirectUri)
                     throws IOException {
 
-        final OIDCClientMetadata metadata = new OIDCClientMetadata();
-        metadata.setGrantTypes(new HashSet<GrantType>(List.of(GrantType.AUTHORIZATION_CODE,
-                GrantType.REFRESH_TOKEN, GrantType.CLIENT_CREDENTIALS)));
+        final OIDCClientMetadata metadata = buildMetadataSkeleton();
         final HashSet<URI> uris = new HashSet<>();
         if (redirectUri != null) {
             for (final String uri : redirectUri) {
@@ -330,16 +347,6 @@ public abstract class AbstractOidcFlowTest extends AbstractFlowTest {
                 }
             }
         }
-        final HashSet<ResponseType> responseTypes = new HashSet<>();
-        responseTypes.add(new ResponseType("code"));
-        // implicit flows
-        responseTypes.add(new ResponseType("id_token"));
-        responseTypes.add(new ResponseType("id_token", "token"));
-        // hybrid flows
-        responseTypes.add(new ResponseType("code", "id_token"));
-        responseTypes.add(new ResponseType("code", "token"));
-        responseTypes.add(new ResponseType("code", "id_token", "token"));
-        metadata.setResponseTypes(responseTypes);
         metadata.setRedirectionURIs(uris);
         metadata.setScope(scope);
         metadata.setTokenEndpointAuthJWSAlg(tokenEndpointSigAlg);
@@ -354,34 +361,56 @@ public abstract class AbstractOidcFlowTest extends AbstractFlowTest {
             metadata.setCustomField("audience", List.of("https://rp.example.org", "https://rp2.example.org",
                     "https://resource.example.org"));
         }
-        final OIDCClientInformation information;
-        if (publicKey == null) {
-            information = new OIDCClientInformation(new ClientID(clientId), new Date(),
-                    metadata, secret != null ? new Secret(secret) : null);
-        } else {
-            final JWKSet jwkSet;
-            if (publicKey instanceof RSAPublicKey) {
-                final RSAKey rsaKey = new RSAKey.Builder((RSAPublicKey) publicKey).build();
-                jwkSet = new JWKSet(rsaKey);
-            } else if (publicKey instanceof ECPublicKey) {
-                final ECPublicKey ecPublicKey = (ECPublicKey) publicKey;
-                final ECKey ecKey = new ECKey.Builder(Curve.forECParameterSpec(ecPublicKey.getParams()),
-                        ecPublicKey).build();
-                jwkSet = new JWKSet(ecKey);
-            } else {
-                Assert.fail();
-                return;
-            }
-            metadata.setJWKSet(jwkSet);
-            information = new OIDCClientInformation(new ClientID(clientId), new Date(),
-                    metadata, secret != null ? new Secret(secret) : null);
+        if (publicKey != null) {
+            metadata.setJWKSet(buildJWKSet(publicKey));
         }
+        storeMetadataObject(storageService, clientId, secret, metadata);
+    }
+
+// Checkstyle: ParameterNumber ON
+
+    protected static void storeMetadataObject(final StorageService storageService, final String clientId, final String secret,
+            final OIDCClientMetadata metadata) throws IOException {
+
+        metadata.setGrantTypes(new HashSet<GrantType>(List.of(GrantType.AUTHORIZATION_CODE,
+                GrantType.REFRESH_TOKEN, GrantType.CLIENT_CREDENTIALS)));
+        final OIDCClientInformation information = new OIDCClientInformation(new ClientID(clientId), new Date(),
+                    metadata, secret != null ? new Secret(secret) : null);
         storageService.create(BaseStorageServiceClientInformationComponent.CONTEXT_NAME, clientId, 
                 information.toJSONObject().toJSONString(), System.currentTimeMillis() + (60 * 60 * 1000));
-        
     }
-// Checkstyle: ParameterNumber ON
-    
+
+    protected static OIDCClientMetadata buildMetadataSkeleton() {
+        final OIDCClientMetadata metadata = new OIDCClientMetadata();
+        metadata.setGrantTypes(new HashSet<GrantType>(List.of(GrantType.AUTHORIZATION_CODE,
+                GrantType.REFRESH_TOKEN, GrantType.CLIENT_CREDENTIALS)));
+        final HashSet<ResponseType> responseTypes = new HashSet<>();
+        responseTypes.add(new ResponseType("code"));
+        // implicit flows
+        responseTypes.add(new ResponseType("id_token"));
+        responseTypes.add(new ResponseType("id_token", "token"));
+        // hybrid flows
+        responseTypes.add(new ResponseType("code", "id_token"));
+        responseTypes.add(new ResponseType("code", "token"));
+        responseTypes.add(new ResponseType("code", "id_token", "token"));
+        metadata.setResponseTypes(responseTypes);
+        return metadata;
+    }
+
+    protected static JWKSet buildJWKSet(final PublicKey publicKey) {
+        if (publicKey instanceof RSAPublicKey) {
+            final RSAKey rsaKey = new RSAKey.Builder((RSAPublicKey) publicKey).build();
+            return new JWKSet(rsaKey);
+        } else if (publicKey instanceof ECPublicKey) {
+            final ECPublicKey ecPublicKey = (ECPublicKey) publicKey;
+            final ECKey ecKey = new ECKey.Builder(Curve.forECParameterSpec(ecPublicKey.getParams()),
+                    ecPublicKey).build();
+            return new JWKSet(ecKey);
+        } else {
+            Assert.fail();
+        }
+        return null;
+    }
     protected void removeMetadata(final StorageService storageService, final String clientId) throws IOException {
         storageService.delete(BaseStorageServiceClientInformationComponent.CONTEXT_NAME, clientId);
     }
@@ -483,10 +512,26 @@ public abstract class AbstractOidcFlowTest extends AbstractFlowTest {
     }
 
     protected static BasicJWKCredential loadEncryptionCredential() {
-        return loadEncryptionCredential("/credentials/idp-encryption-rsa.jwk");
+        return loadCredential("/credentials/idp-encryption-rsa.jwk");
+    }
+
+    protected static BasicJWKCredential loadRSSigningCredential() {
+        return loadCredential("/credentials/idp-signing-rs.jwk");
     }
 
-    protected static BasicJWKCredential loadEncryptionCredential(final String classPathLocation) {
+    protected static BasicJWKCredential loadESSigningCredential() {
+        return loadCredential("/credentials/idp-signing-es.jwk");
+    }
+
+    protected static BasicJWKCredential loadES384SigningCredential() {
+        return loadCredential("/credentials/idp-signing-es-384.jwk");
+    }
+
+    protected static BasicJWKCredential loadES512SigningCredential() {
+        return loadCredential("/credentials/idp-signing-es-521.jwk");
+    }
+
+    protected static BasicJWKCredential loadCredential(final String classPathLocation) {
         final BasicJWKCredentialFactoryBean factory = new BasicJWKCredentialFactoryBean();
         factory.setResource(new ClassPathResource(classPathLocation));
         try {
@@ -498,4 +543,36 @@ public abstract class AbstractOidcFlowTest extends AbstractFlowTest {
         }
     }
 
+    protected BearerAccessToken buildToken(final String clientId, final String subject, final Scope scope)
+            throws URISyntaxException, NoSuchAlgorithmException, DataSealerException, ComponentInitializationException {
+        return buildToken(clientId, subject, scope, null);
+    }
+
+    protected BearerAccessToken buildToken(final String clientId, final String subject, final Scope scope,
+            final ClaimsSet userInfoDeliverySet)
+            throws URISyntaxException, NoSuchAlgorithmException, DataSealerException, ComponentInitializationException {
+        return buildToken(clientId, subject, scope, userInfoDeliverySet, null, null);
+    }
+
+    protected BearerAccessToken buildToken(final String clientId, final String subject, final Scope scope,
+            final ClaimsSet userInfoDeliverySet, final String id, final String rootId)
+            throws URISyntaxException, NoSuchAlgorithmException, DataSealerException, ComponentInitializationException {
+        final String jti = id == null ? idGenerator.generateIdentifier() : id;
+        final TokenClaimsSet claims = new AccessTokenClaimsSet.Builder()
+                .setJWTID(jti)
+                .setClientID(new ClientID(clientId))
+                .setIssuer("https://op.example.org")
+                .setPrincipal("jdoe")
+                .setSubject(subject)
+                .setIssuedAt(Instant.now())
+                .setExpiresAt(Instant.now().plusSeconds(30))
+                .setAuthenticationTime(Instant.now())
+                .setRedirectURI(new URI("https://example.org/cb"))
+                .setScope(scope)
+                .setDlClaimsUI(userInfoDeliverySet)
+                .setRootTokenIdentifier(rootId)
+                .build();
+        return new BearerAccessToken(claims.serialize(BaseOIDCResponseActionTest.initializeDataSealer()));
+    }
+
 }
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AuthorizeFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AuthorizeFlowTest.java
index 17fb4b7e..eeb7ffd2 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AuthorizeFlowTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AuthorizeFlowTest.java
@@ -33,10 +33,12 @@ import java.util.List;
 import org.opensaml.storage.StorageService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.mock.web.MockHttpServletRequest;
 import org.springframework.webflow.executor.FlowExecutionResult;
 import org.testng.Assert;
 import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Factory;
 import org.testng.annotations.Test;
 
 import com.nimbusds.jose.EncryptionMethod;
@@ -1539,6 +1541,18 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
                 JWSAlgorithm.ES512, ecKey.toECPublicKey(), JWEAlgorithm.RSA_OAEP, EncryptionMethod.A128CBC_HS256);
     }
 
+    @Factory
+    public Object[] createIdTokenSecurityTests() {
+        return new Object[] {
+                new IssuedJWTSignatureTest(IssuedJWTSignatureTest.JWT_FETCHING_TYPE.AUTHORIZE_ID_TOKEN, FLOW_ID) };
+    }
+
+    @Factory
+    public Object[] createAccessTokenSecurityTests() {
+        return new Object[] {
+                new IssuedJWTSignatureTest(IssuedJWTSignatureTest.JWT_FETCHING_TYPE.AUTHORIZE_ACCESS_TOKEN, FLOW_ID) };
+    }
+
     protected String getRequestObjectWithClaimsRequestPayload() {
         return "{\n"
                 + "  \"iss\": \"" + clientId + "\",\n"
@@ -1732,6 +1746,11 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
     }
     
     protected void setRequestParameters(final List<Pair<String, String>> pairs) {
+        setRequestParameters(request, pairs);
+    }
+
+    protected static void setRequestParameters(final MockHttpServletRequest request,
+            final List<Pair<String, String>> pairs) {
         final StringBuffer query = new StringBuffer();
         for (final Pair<String, String> pair : pairs) {
             request.addParameter(pair.getFirst(), pair.getSecond());
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/IssuedJWTSignatureTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/IssuedJWTSignatureTest.java
new file mode 100644
index 00000000..31c79440
--- /dev/null
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/IssuedJWTSignatureTest.java
@@ -0,0 +1,404 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.oidc.op.profile.flow;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.interfaces.ECPublicKey;
+import java.security.interfaces.RSAPublicKey;
+import java.text.ParseException;
+import java.util.List;
+
+import org.springframework.webflow.executor.FlowExecutionResult;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.nimbusds.jose.EncryptionMethod;
+import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.JWEAlgorithm;
+import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jose.JWSVerifier;
+import com.nimbusds.jose.crypto.ECDSAVerifier;
+import com.nimbusds.jose.crypto.RSASSAVerifier;
+import com.nimbusds.jwt.JWT;
+import com.nimbusds.jwt.SignedJWT;
+import com.nimbusds.oauth2.sdk.AccessTokenResponse;
+import com.nimbusds.oauth2.sdk.Scope;
+import com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod;
+import com.nimbusds.oauth2.sdk.auth.ClientSecretJWT;
+import com.nimbusds.oauth2.sdk.token.AccessToken;
+import com.nimbusds.oauth2.sdk.token.BearerAccessToken;
+import com.nimbusds.openid.connect.sdk.AuthenticationResponse;
+import com.nimbusds.openid.connect.sdk.AuthenticationSuccessResponse;
+import com.nimbusds.openid.connect.sdk.OIDCTokenResponse;
+import com.nimbusds.openid.connect.sdk.UserInfoSuccessResponse;
+import com.nimbusds.openid.connect.sdk.rp.OIDCClientMetadata;
+
+import net.shibboleth.utilities.java.support.collection.Pair;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.security.DataSealerException;
+
+/**
+ * Tests for verifying that the signature on JWT issued by OP matches the expected configuration. OP may issue JWTs
+ * from authorize, token and userinfo endpoints, depending on the RP or resource metadata.
+ */
+public class IssuedJWTSignatureTest extends AbstractOidcFlowTest {
+    
+    String defaultClientId = "mockClientId";
+    String defaultClientSecret = 
+            "mockClientSecretmockClientSecretmockClientSecretmockClientSecretmockClientSecret";
+
+
+    public enum JWT_FETCHING_TYPE {
+        
+        USERINFO,
+        
+        TOKEN_ID_TOKEN,
+        
+        TOKEN_ACCESS_TOKEN,
+        
+        AUTHORIZE_ID_TOKEN,
+        
+        AUTHORIZE_ACCESS_TOKEN,
+    }
+    
+    private final JWT_FETCHING_TYPE fetchingType;
+    
+    public IssuedJWTSignatureTest(final JWT_FETCHING_TYPE type, final String flowId) {
+        super(flowId);
+        fetchingType = type;
+    }
+    
+    protected JWT obtainJwt(final JWSAlgorithm jwsAlgorithm, final JWEAlgorithm jweAlgorithm, final EncryptionMethod encryptionMethod) {
+        switch (fetchingType) {
+            case USERINFO:
+                return obtainUserInfoAsJwt(defaultClientId, jwsAlgorithm, jweAlgorithm, encryptionMethod);
+            case TOKEN_ID_TOKEN:
+                return obtainIdTokenFromTokenEndpoint(defaultClientId, jwsAlgorithm, jweAlgorithm, encryptionMethod);
+            case TOKEN_ACCESS_TOKEN:
+                return obtainJwtAccessTokenFromTokenEndpoint("mockClientIdJwtAccessToken", jwsAlgorithm, jweAlgorithm,
+                        encryptionMethod);
+            case AUTHORIZE_ID_TOKEN:
+                return obtainIdTokenFromAuthorizeEndpoint(defaultClientId, jwsAlgorithm, jweAlgorithm,
+                        encryptionMethod);
+            case AUTHORIZE_ACCESS_TOKEN:
+                return obtainAccessTokenFromAuthorizeEndpoint(defaultClientId, jwsAlgorithm, jweAlgorithm,
+                        encryptionMethod);
+            default:
+                Assert.fail();
+        }
+        return null;
+    }
+    
+    @Test
+    public void testJwtSecurity_jwtSigAlgAndEncNotSpecified() throws Exception {
+        if (fetchingType.equals(JWT_FETCHING_TYPE.USERINFO)) {
+            // No signing done on UserInfo by default
+            final UserInfoSuccessResponse response = obtainUserInfoResponse(defaultClientId, null, null, null);
+            Assert.assertNotNull(response.getUserInfo());
+            Assert.assertNull(response.getUserInfoJWT());
+        } else {
+            final JWT jwt = obtainJwt(null, null, null);
+            assertSignedJwt(jwt, JWSAlgorithm.RS256, loadRSSigningCredential().getPublicKey());
+        }
+    }
+
+    @Test
+    public void testJwtSecurity_jwtRS256SigAlgAndEncNotSpecified() throws Exception {
+        final JWT jwt = obtainJwt(JWSAlgorithm.RS256, null, null);
+        assertSignedJwt(jwt, JWSAlgorithm.RS256, loadRSSigningCredential().getPublicKey());
+    }
+
+    @Test
+    public void testJwtSecurity_jwtRS384SigAlgAndEncNotSpecified() throws Exception {
+        final JWT jwt = obtainJwt(JWSAlgorithm.RS384, null, null);
+        assertSignedJwt(jwt, JWSAlgorithm.RS384, loadRSSigningCredential().getPublicKey());
+    }
+
+    @Test
+    public void testJwtSecurity_jwtRS512SigAlgAndEncNotSpecified() throws Exception {
+        final JWT jwt = obtainJwt(JWSAlgorithm.RS512, null, null);
+        assertSignedJwt(jwt, JWSAlgorithm.RS512, loadRSSigningCredential().getPublicKey());
+    }
+
+    @Test
+    public void testJwtSecurity_jwtPS256SigAlgAndEncNotSpecified() throws Exception {
+        final JWT jwt = obtainJwt(JWSAlgorithm.PS256, null, null);
+        assertSignedJwt(jwt, JWSAlgorithm.PS256, loadRSSigningCredential().getPublicKey());
+    }
+
+    @Test
+    public void testJwtSecurity_jwtPS384SigAlgAndEncNotSpecified() throws Exception {
+        final JWT jwt = obtainJwt(JWSAlgorithm.PS384, null, null);
+        assertSignedJwt(jwt, JWSAlgorithm.PS384, loadRSSigningCredential().getPublicKey());
+    }
+
+    @Test
+    public void testJwtSecurity_jwtPS512SigAlgAndEncNotSpecified() throws Exception {
+        final JWT jwt = obtainJwt(JWSAlgorithm.PS512, null, null);
+        assertSignedJwt(jwt, JWSAlgorithm.PS512, loadRSSigningCredential().getPublicKey());
+    }
+
+    @Test
+    public void testJwtSecurity_jwtES256SigAlgAndEncNotSpecified() throws Exception {
+        final JWT jwt = obtainJwt(JWSAlgorithm.ES256, null, null);
+        assertSignedJwt(jwt, JWSAlgorithm.ES256, loadESSigningCredential().getPublicKey());
+    }
+
+    @Test
+    public void testJwtSecurity_jwtES384SigAlgAndEncNotSpecified() throws Exception {
+        // ES384 is globally excluded
+        if (fetchingType.equals(JWT_FETCHING_TYPE.USERINFO)) {
+            // UserInfo responds raw JSON when alg is disabled
+            final UserInfoSuccessResponse response = obtainUserInfoResponse(defaultClientId, JWSAlgorithm.ES384, null,
+                    null);
+            Assert.assertNotNull(response.getUserInfo());
+            Assert.assertNull(response.getUserInfoJWT());
+        } else {
+            final JWT jwt = obtainJwt(JWSAlgorithm.ES384, null, null);
+            Assert.assertNull(jwt);
+        }
+    }
+
+    @Test
+    public void testJwtSecurity_jwtES512SigAlgAndEncNotSpecified() throws Exception {
+        final JWT jwt = obtainJwt(JWSAlgorithm.ES512, null, null);
+        assertSignedJwt(jwt, JWSAlgorithm.ES512, loadCredential("/credentials/idp-signing-es521.jwk").getPublicKey());
+    }
+
+    protected static void assertSignedJwt(final JWT jwt, final JWSAlgorithm algorithm, final PublicKey publicKey) {
+        Assert.assertTrue(SignedJWT.class.isInstance(jwt));
+        final SignedJWT signedJwt = (SignedJWT) jwt;
+        Assert.assertEquals(signedJwt.getHeader().getAlgorithm(), algorithm);
+        final JWSVerifier verifier;
+        try {
+            if (JWSAlgorithm.Family.RSA.contains(algorithm) && publicKey instanceof RSAPublicKey) {
+                verifier =  new RSASSAVerifier((RSAPublicKey) publicKey);
+            } else if (JWSAlgorithm.Family.EC.contains(algorithm) && publicKey instanceof ECPublicKey) {
+                verifier = new ECDSAVerifier((ECPublicKey) publicKey);
+            } else {
+                Assert.fail();
+                return;
+            }
+            Assert.assertTrue(signedJwt.verify(verifier));
+        } catch (JOSEException e) {
+            Assert.fail();
+        }
+    }
+    
+    public UserInfoSuccessResponse obtainUserInfoResponse(final String clientId, final JWSAlgorithm jwsAlgorithm, final JWEAlgorithm jweAlgorithm,
+            final EncryptionMethod encryptionMethod) {
+        request.setMethod("GET");
+        try {
+            removeMetadata(storageService, clientId);
+        } catch (final IOException e) {
+            Assert.fail();
+        }
+        BearerAccessToken token;
+        try {
+            token = buildToken(clientId, "mockSubject", new Scope("openid"));
+        } catch (final NoSuchAlgorithmException | URISyntaxException | DataSealerException
+                | ComponentInitializationException e) {
+            Assert.fail();
+            return null;
+        }
+        final OIDCClientMetadata metadata = new OIDCClientMetadata();
+        metadata.setScope(new Scope("openid"));
+        metadata.setUserInfoJWSAlg(jwsAlgorithm);
+        metadata.setUserInfoJWEAlg(jweAlgorithm);
+        metadata.setUserInfoJWEEnc(encryptionMethod);
+        try {
+            storeMetadataObject(storageService, clientId, "mockSecret", metadata);
+        } catch (final IOException e) {
+            Assert.fail();
+            return null;
+        }
+        request.addHeader("Authorization", token.toAuthorizationHeader());
+        final FlowExecutionResult result = flowExecutor.launchExecution(flowId, null, externalContext);
+        try {
+            removeMetadata(storageService, clientId);
+        } catch (final IOException e) {
+            Assert.fail();
+        }
+        return parseSuccessResponse(result, UserInfoSuccessResponse.class);
+    }
+
+    public JWT obtainUserInfoAsJwt(final String clientId, final JWSAlgorithm jwsAlgorithm, final JWEAlgorithm jweAlgorithm,
+            final EncryptionMethod encryptionMethod) {
+        final UserInfoSuccessResponse response = obtainUserInfoResponse(clientId, jwsAlgorithm, jweAlgorithm, encryptionMethod);
+        Assert.assertNull(response.getUserInfo());
+        Assert.assertNotNull(response.getUserInfoJWT());
+        return response.getUserInfoJWT();
+    }
+
+    protected JWT obtainIdTokenFromTokenEndpoint(final String clientId, final JWSAlgorithm storedJwsAlgorithm, final JWEAlgorithm storedJweAlgorithm,
+            final EncryptionMethod storedJweMethod) {
+        try {
+            final ClientSecretJWT clientAuth = TokenFlowTest.buildSecretJwtAuth(clientId, defaultClientSecret, "http://localhost");
+            final OIDCClientMetadata metadata = buildMetadataSkeleton();
+            metadata.setScope(Scope.parse("openid profile email offline_access"));
+            metadata.setTokenEndpointAuthJWSAlg(JWSAlgorithm.HS256);
+            metadata.setTokenEndpointAuthMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT);
+            metadata.setIDTokenJWEAlg(storedJweAlgorithm);
+            metadata.setIDTokenJWSAlg(storedJwsAlgorithm);
+            metadata.setIDTokenJWEEnc(storedJweMethod);
+            final FlowExecutionResult result = TokenFlowTest.launchWithJwtAuthentication(flowExecutor, clientAuth,
+                    externalContext, request, "http://localhost", "openid", metadata, defaultClientSecret,
+                    storageService);
+            removeMetadata(storageService, clientId);
+            if (parseResponse(result).indicatesSuccess()) {
+                final OIDCTokenResponse response = parseSuccessResponse(result, OIDCTokenResponse.class);
+                Assert.assertNotNull(response.getOIDCTokens().getIDToken());
+                return response.getOIDCTokens().getIDToken();
+            } else {
+                return null;
+            }
+        } catch (final Exception e) {
+            Assert.fail();
+            return null;
+        }
+    }
+
+    protected JWT obtainJwtAccessTokenFromTokenEndpoint(final String clientId, final JWSAlgorithm storedJwsAlgorithm,
+            final JWEAlgorithm storedJweAlgorithm, final EncryptionMethod storedJweMethod) {
+        try {
+            final ClientSecretJWT clientAuth = TokenFlowTest.buildSecretJwtAuth(clientId, defaultClientSecret, "http://localhost");
+            final OIDCClientMetadata metadata = buildMetadataSkeleton();
+            metadata.setScope(Scope.parse("openid profile email offline_access"));
+            metadata.setTokenEndpointAuthJWSAlg(JWSAlgorithm.HS256);
+            metadata.setTokenEndpointAuthMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT);
+            metadata.setIDTokenJWEAlg(storedJweAlgorithm);
+            metadata.setIDTokenJWSAlg(storedJwsAlgorithm);
+            metadata.setIDTokenJWEEnc(storedJweMethod);
+            final FlowExecutionResult result = TokenFlowTest.launchWithJwtAuthentication(flowExecutor, clientAuth,
+                    externalContext, request, "http://localhost", "openid", metadata, defaultClientSecret,
+                    storageService);
+            removeMetadata(storageService, clientId);
+            if (parseResponse(result).indicatesSuccess()) {
+                final AccessTokenResponse response = parseSuccessResponse(result, AccessTokenResponse.class);
+                Assert.assertNotNull(response.getTokens().getAccessToken());
+                final AccessToken accessToken = response.getTokens().getAccessToken();
+                return SignedJWT.parse(accessToken.getValue());
+            } else {
+                return null;
+            }
+        } catch (final Exception e) {
+            Assert.fail(e.getMessage(), e);
+            return null;
+        }
+    }
+
+    protected JWT obtainIdTokenFromAuthorizeEndpoint(final String clientId, final JWSAlgorithm storedJwsAlgorithm,
+            final JWEAlgorithm storedJweAlgorithm, final EncryptionMethod storedJweMethod) {
+        setBasicAuth("jdoe", "changeit");
+        request.setMethod("GET");
+        final String redirectUri = "https://example.org/cb";
+        AuthorizeFlowTest.setRequestParameters(request, List.of(new Pair<>("client_id", clientId),
+                new Pair<>("response_type", "id_token"),
+                new Pair<>("scope", "openid profile"),
+                new Pair<>("redirect_uri", redirectUri),
+                new Pair<>("nonce", "idhas3h23hi13h1o2i32")));
+        final OIDCClientMetadata metadata = buildMetadataSkeleton();
+        metadata.setScope(Scope.parse("openid profile email offline_access"));
+        metadata.setIDTokenJWEAlg(storedJweAlgorithm);
+        metadata.setIDTokenJWSAlg(storedJwsAlgorithm);
+        metadata.setIDTokenJWEEnc(storedJweMethod);
+        try {
+            metadata.setRedirectionURI(new URI(redirectUri));
+            super.storeMetadataObject(storageService, clientId, defaultClientSecret, metadata);
+        } catch (final IOException | URISyntaxException e) {
+            Assert.fail(e.getMessage(), e);
+        }
+
+        initializeThreadLocals();
+        
+        final FlowExecutionResult result = flowExecutor.launchExecution(flowId, null, externalContext);
+        try {
+            super.removeMetadata(storageService, clientId);
+        } catch (final IOException e) {
+            Assert.fail(e.getMessage(), e);
+        }
+        if (parseResponse(result).indicatesSuccess()) {
+            final AuthenticationResponse responseMessage = parseSuccessResponse(result, AuthenticationResponse.class);
+            final AuthenticationSuccessResponse successResponse = responseMessage.toSuccessResponse();
+            Assert.assertEquals(successResponse.getRedirectionURI().toString(), redirectUri);
+            Assert.assertNotNull(successResponse.getIDToken());
+            return successResponse.getIDToken();
+        }
+        return null;
+    }
+
+    protected JWT obtainAccessTokenFromAuthorizeEndpoint(final String clientId, final JWSAlgorithm storedJwsAlgorithm,
+            final JWEAlgorithm storedJweAlgorithm, final EncryptionMethod storedJweMethod) {
+        setBasicAuth("jdoe", "changeit");
+        request.setMethod("GET");
+        final String redirectUri = "https://example.org/cb";
+        final String resource = "https://mock.example.org";
+
+        AuthorizeFlowTest.setRequestParameters(request, List.of(new Pair<>("client_id", clientId),
+                new Pair<>("response_type", "id_token token"),
+                new Pair<>("scope", "openid profile"),
+                new Pair<>("redirect_uri", redirectUri),
+                new Pair<>("resource", resource),
+                new Pair<>("nonce", "idhas3h23hi13h1o2i32")));
+
+        final OIDCClientMetadata metadata = buildMetadataSkeleton();
+        metadata.setScope(Scope.parse("openid profile email offline_access"));
+        metadata.setCustomField("audience", List.of(resource));
+
+        final OIDCClientMetadata resourceMetadata = buildMetadataSkeleton();
+        resourceMetadata.setScope(Scope.parse("openid profile email offline_access"));
+        resourceMetadata.setIDTokenJWEAlg(storedJweAlgorithm);
+        resourceMetadata.setIDTokenJWSAlg(storedJwsAlgorithm);
+        resourceMetadata.setIDTokenJWEEnc(storedJweMethod);
+
+        try {
+            metadata.setRedirectionURI(new URI(redirectUri));
+            super.storeMetadataObject(storageService, clientId, defaultClientSecret, metadata);
+            super.storeMetadataObject(storageService, resource, defaultClientSecret, resourceMetadata);
+        } catch (final IOException | URISyntaxException e) {
+            Assert.fail(e.getMessage(), e);
+        }
+
+        initializeThreadLocals();
+        
+        final FlowExecutionResult result = flowExecutor.launchExecution(flowId, null, externalContext);
+        try {
+            super.removeMetadata(storageService, clientId);
+            super.removeMetadata(storageService, resource);
+        } catch (final IOException e) {
+            Assert.fail(e.getMessage(), e);
+        }
+        if (parseResponse(result).indicatesSuccess()) {
+            final AuthenticationResponse responseMessage = parseSuccessResponse(result, AuthenticationResponse.class);
+            final AuthenticationSuccessResponse successResponse = responseMessage.toSuccessResponse();
+            Assert.assertEquals(successResponse.getRedirectionURI().toString(), redirectUri);
+            Assert.assertNotNull(successResponse.getAccessToken());
+            try {
+                return SignedJWT.parse(successResponse.getAccessToken().getValue());
+            } catch (final ParseException e) {
+                Assert.fail(e.getMessage(), e);
+            }
+        }
+        return null;
+    }
+
+}
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/TokenFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/TokenFlowTest.java
index ce1def17..77d12f5d 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/TokenFlowTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/TokenFlowTest.java
@@ -32,11 +32,17 @@ import org.opensaml.storage.RevocationCache;
 import org.opensaml.storage.StorageService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.webflow.context.ExternalContext;
 import org.springframework.webflow.executor.FlowExecutionResult;
+import org.springframework.webflow.executor.FlowExecutor;
 import org.testng.Assert;
 import org.testng.annotations.AfterMethod;
+import org.testng.annotations.Factory;
 import org.testng.annotations.Test;
 
+import com.nimbusds.jose.EncryptionMethod;
+import com.nimbusds.jose.JWEAlgorithm;
 import com.nimbusds.jose.JWSAlgorithm;
 import com.nimbusds.jwt.JWT;
 import com.nimbusds.oauth2.sdk.AccessTokenResponse;
@@ -55,6 +61,7 @@ import com.nimbusds.oauth2.sdk.token.RefreshToken;
 import com.nimbusds.openid.connect.sdk.OIDCTokenResponse;
 import com.nimbusds.openid.connect.sdk.claims.ClaimsSet;
 import com.nimbusds.openid.connect.sdk.claims.IDTokenClaimsSet;
+import com.nimbusds.openid.connect.sdk.rp.OIDCClientMetadata;
 
 import net.minidev.json.JSONObject;
 import net.shibboleth.idp.plugin.oidc.op.profile.impl.ValidateGrantTest;
@@ -84,6 +91,7 @@ public class TokenFlowTest extends AbstractOidcClientAuthenticationFlowTest {
     String clientIdPkceS256Public = "mockPublicClientIdPKCES256";
     String clientIdCustomTokens = "mockClientIdCustomTokens";
     String clientIdRefreshTokenRotation = "mockClientIdRefreshTokenRotation";
+    String clientIdJwtAccessToken = "mockClientIdJwtAccessToken";
     String codeVerifier = "9234567812345678123456781234567812345678123456781234567812345678";
 
     Scope scope = Scope.parse("openid profile email offline_access");
@@ -107,8 +115,9 @@ public class TokenFlowTest extends AbstractOidcClientAuthenticationFlowTest {
         removeMetadata(storageService, clientIdPkceS256);
         removeMetadata(storageService, clientIdCustomTokens);
         removeMetadata(storageService, clientIdRefreshTokenRotation);
+        removeMetadata(storageService, clientIdJwtAccessToken);
     }
-    
+
     @Test
     public void testNoClientId() throws IOException, ParseException {
         setHttpFormRequest("POST", createRequestParameters(redirectUri, "authorization_code", "mockCode", null));
@@ -853,6 +862,18 @@ public class TokenFlowTest extends AbstractOidcClientAuthenticationFlowTest {
         Assert.assertTrue(revocationCache.isRevoked(RevocationCacheContexts.AUTHORIZATION_CODE, id));
     }
 
+    @Factory
+    public Object[] createIdTokenSecurityTests() {
+        return new Object[] {
+                new IssuedJWTSignatureTest(IssuedJWTSignatureTest.JWT_FETCHING_TYPE.TOKEN_ID_TOKEN, FLOW_ID) };
+    }
+
+    @Factory
+    public Object[] createAccessTokenSecurityTests() {
+        return new Object[] {
+                new IssuedJWTSignatureTest(IssuedJWTSignatureTest.JWT_FETCHING_TYPE.TOKEN_ACCESS_TOKEN, FLOW_ID) };
+    }
+
     private AccessTokenClaimsSet unwrapAccessToken(final OIDCTokenResponse tokenResponse) {
         final AccessToken accessToken = tokenResponse.getTokens().getAccessToken();
         Assert.assertNotNull(accessToken);
@@ -895,6 +916,39 @@ public class TokenFlowTest extends AbstractOidcClientAuthenticationFlowTest {
         return flowExecutor.launchExecution(FLOW_ID, null, externalContext);
     }
 
+    protected FlowExecutionResult launchWithJwtAuthentication(final JWTAuthentication authnMethod,
+            final JWSAlgorithm algorithm, final JWSAlgorithm idTokenSigAlg, final JWEAlgorithm idTokenEncAlg,
+            final EncryptionMethod idTokenEncMethod)
+            throws Exception {
+        final OIDCClientMetadata metadata = buildMetadataSkeleton();
+        metadata.setScope(scope);
+        metadata.setTokenEndpointAuthMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT);
+        metadata.setTokenEndpointAuthJWSAlg(JWSAlgorithm.HS256);
+        metadata.setIDTokenJWSAlg(idTokenSigAlg);
+        metadata.setIDTokenJWEAlg(idTokenEncAlg);
+        metadata.setIDTokenJWEEnc(idTokenEncMethod);
+        return launchWithJwtAuthentication(flowExecutor, authnMethod, externalContext, request, redirectUri,
+                scope.toString(), metadata, clientSecret, storageService);
+    }
+
+    protected static FlowExecutionResult launchWithJwtAuthentication(final FlowExecutor flowExecutor,
+            final JWTAuthentication authnMethod, final ExternalContext externalContext,
+            final MockHttpServletRequest request, final String redirectUri, final String scope,
+            final OIDCClientMetadata metadata, final String clientSecret, final StorageService storageService)
+            throws Exception {
+        final String clientId = authnMethod.getClientID().getValue();
+        final String code = ValidateGrantTest.buildAuthorizationCode(clientId, "https://op.example.org", "jdoe"
+                , "mock",
+                redirectUri, scope.toString()).toString();
+        storeMetadataObject(storageService, clientId, clientSecret, metadata);
+        final Map<String, String> requestParameters =
+                createRequestParameters(redirectUri, "authorization_code", code, clientId);
+        populateClientAssertionParams(requestParameters, authnMethod);
+        setHttpFormRequest(request, "POST", requestParameters);
+        return flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+
+    }
+
     protected FlowExecutionResult launchWithJwtAuthentication(final JWT jwt, final JWSAlgorithm algorithm,
             final ClientAuthenticationMethod method, final PublicKey publicKey) throws Exception {
         final String code = ValidateGrantTest.buildAuthorizationCode(clientId, "https://op.example.org", "jdoe", "mock",
@@ -911,7 +965,7 @@ public class TokenFlowTest extends AbstractOidcClientAuthenticationFlowTest {
         return flowExecutor.launchExecution(FLOW_ID, null, externalContext);
     }
 
-    protected Map<String, String> createRequestParameters(final String redirectUri, final String grantType,
+    protected static Map<String, String> createRequestParameters(final String redirectUri, final String grantType,
             final String code,  final String clientId) {
         final Map<String, String> parameters = new HashMap<>();
         addNonNullValue(parameters, "redirect_uri", redirectUri);
@@ -925,7 +979,7 @@ public class TokenFlowTest extends AbstractOidcClientAuthenticationFlowTest {
         return parameters;
     }
     
-    protected Map<String, String> createRequestParameters(final String redirectUri, final String grantType,
+    protected static Map<String, String> createRequestParameters(final String redirectUri, final String grantType,
             final String code, final String clientId, final String codeChallenge, final String codeChallengeMethod,
             final String codeVerifier) {
         final Map<String, String> parameters = createRequestParameters(redirectUri, grantType, code, clientId);
@@ -935,7 +989,7 @@ public class TokenFlowTest extends AbstractOidcClientAuthenticationFlowTest {
         return parameters;
     }
     
-    private void addNonNullValue(final Map<String, String> map, final String key, final String value) {
+    private static void addNonNullValue(final Map<String, String> map, final String key, final String value) {
         if (value != null) {
             map.put(key, value);
         }
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/UserInfoTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/UserInfoTest.java
index 3662722c..a5de9adc 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/UserInfoTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/UserInfoTest.java
@@ -22,15 +22,14 @@ import java.net.URISyntaxException;
 import java.security.NoSuchAlgorithmException;
 import java.text.ParseException;
 
-import org.opensaml.profile.action.EventIds;
 import org.opensaml.storage.RevocationCache;
-import org.opensaml.storage.StorageService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.webflow.executor.FlowExecutionResult;
 import org.testng.Assert;
 import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Factory;
 import org.testng.annotations.Test;
 
 import com.nimbusds.jose.JWSAlgorithm;
@@ -63,10 +62,6 @@ public class UserInfoTest extends AbstractOidcApiFlowTest {
 
     Scope scope = Scope.parse("openid profile email");
     
-    @Autowired
-    @Qualifier("shibboleth.StorageService")
-    StorageService storageService;
-
     @Autowired
     @Qualifier("shibboleth.oidc.RevocationCache")
     private RevocationCache revocationCache;
@@ -74,7 +69,7 @@ public class UserInfoTest extends AbstractOidcApiFlowTest {
     public UserInfoTest() {
         super(FLOW_ID);
     }
-    
+
     @BeforeMethod
     public void init() throws IOException {
         request.setMethod("GET");
@@ -334,4 +329,10 @@ public class UserInfoTest extends AbstractOidcApiFlowTest {
         assertErrorCode(result, BearerTokenError.INVALID_TOKEN.getCode());
     }
 
+    @Factory
+    public Object[] createUserInfoAsJwtSecurityTests() {
+        return new Object[] {
+                new IssuedJWTSignatureTest(IssuedJWTSignatureTest.JWT_FETCHING_TYPE.USERINFO, FLOW_ID) };
+    }
+
 }
diff --git a/idp-oidc-extension-impl/src/test/resources/conf/relying-party.xml b/idp-oidc-extension-impl/src/test/resources/conf/relying-party.xml
index d2bc1e84..0eac39ea 100644
--- a/idp-oidc-extension-impl/src/test/resources/conf/relying-party.xml
+++ b/idp-oidc-extension-impl/src/test/resources/conf/relying-party.xml
@@ -88,6 +88,14 @@
                  </list>
             </property>
         </bean>
+        <bean parent="RelyingPartyByName" c:relyingPartyIds="mockClientIdJwtAccessToken">
+            <property name="profileConfigurations">
+                 <list>
+                     <bean parent="OIDC.SSO.MDDriven" />
+                     <bean parent="OAUTH2.Token.MDDriven" p:accessTokenType="JWT" p:encryptionOptional="true" />
+                 </list>
+            </property>
+        </bean>
         <bean parent="RelyingPartyByName" c:relyingPartyIds="mockClientIdNoRefreshTokensInSSOProfile">
             <property name="profileConfigurations">
                  <list>
@@ -151,7 +159,7 @@
             </property>
         </bean>
         <bean parent="RelyingPartyByName"
-                c:relyingPartyIds="#{{'https://rp.example.org', 'https://resource.example.org'}}">
+                c:relyingPartyIds="#{{'https://rp.example.org', 'https://resource.example.org', 'https://mock.example.org'}}">
             <property name="profileConfigurations">
                  <list>
                      <bean parent="OAUTH2.TokenAudience.MDDriven" p:accessTokenType="JWT" p:encryptionOptional="true" />

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


More information about the commits mailing list