[java-idp-oidc] branch main updated: JOIDC-11 - Support for client_credentials grant

Scott Cantor cantor.2 at osu.edu
Wed Feb 2 20:36:03 UTC 2022


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

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

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

The following commit(s) were added to refs/heads/main by this push:
     new 21bc27b5 JOIDC-11 - Support for client_credentials grant
21bc27b5 is described below

commit 21bc27b5fbf2fd824f7cc20bce1fc0499120b214
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Wed Feb 2 15:36:00 2022 -0500

    JOIDC-11 - Support for client_credentials grant
    
    https://shibboleth.atlassian.net/browse/JOIDC-11
    
    Implement redesigned revocation flow.
---
 ...xt.java => OAuth2TokenMgmtResponseContext.java} |   9 +-
 .../idp/plugin/oidc/op/profile/OidcEventIds.java   |  12 +-
 ...ection.java => AbstractProcessTokenAction.java} |  56 +++--
 .../FormOutboundIntrospectionResponseMessage.java  |  10 +-
 .../FormOutboundRevokeTokenResponseMessage.java    |   7 +-
 ...zeOutboundTokenMgmtResponseMessageContext.java} |  11 +-
 .../profile/impl/ProcessTokenForIntrospection.java | 239 +--------------------
 ...Context.java => ProcessTokenForRevocation.java} |  22 +-
 .../oidc/op/oauth2/profile/impl/RevokeToken.java   |  76 +++----
 .../profile/impl/AbstractOIDCResponseAction.java   |   4 +-
 .../oauth2/introspection/introspection-beans.xml   |   2 +-
 .../oauth2/introspection/introspection-flow.xml    |   4 +
 .../flows/oauth2/revocation/revocation-beans.xml   |  11 +-
 .../flows/oauth2/revocation/revocation-flow.xml    |  10 +-
 .../oidc/abstract-api/oidc-abstract-api-beans.xml  |   9 +-
 .../idp/service/relying-party/postconfig.xml       |  50 ++++-
 .../op/oauth2/profile/impl/RevokeTokenTest.java    |  89 +++-----
 .../oidc/op/profile/flow/RevocationFlowTest.java   |   2 -
 18 files changed, 210 insertions(+), 413 deletions(-)

diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/messaging/context/OAuth2TokenIntrospectionResponseContext.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/messaging/context/OAuth2TokenMgmtResponseContext.java
similarity index 85%
rename from idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/messaging/context/OAuth2TokenIntrospectionResponseContext.java
rename to idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/messaging/context/OAuth2TokenMgmtResponseContext.java
index 21b3a96e..9958a0c2 100644
--- a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/messaging/context/OAuth2TokenIntrospectionResponseContext.java
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/messaging/context/OAuth2TokenMgmtResponseContext.java
@@ -26,13 +26,14 @@ import org.opensaml.messaging.context.MessageContext;
 import com.nimbusds.jwt.JWTClaimsSet;
 
 /**
- * Subcontext carrying information for an OAuth token introspection response.
+ * Subcontext carrying information for an OAuth token management responses such as introspection
+ * or revocation.
  * 
  * <p>This context appears as a subcontext of a {@link MessageContext}.</p>
  * 
  * @since 3.1.0
  */
-public class OAuth2TokenIntrospectionResponseContext extends BaseContext {
+public class OAuth2TokenMgmtResponseContext extends BaseContext {
 
     /** The token claim set. */
     @Nullable private JWTClaimsSet tokenClaimsSet;
@@ -42,7 +43,7 @@ public class OAuth2TokenIntrospectionResponseContext extends BaseContext {
      * 
      * @return token claims set
      */
-    @Nullable public JWTClaimsSet getTokenClaimSet() {
+    @Nullable public JWTClaimsSet getTokenClaimsSet() {
         return tokenClaimsSet;
     }
 
@@ -53,7 +54,7 @@ public class OAuth2TokenIntrospectionResponseContext extends BaseContext {
      * 
      * @return this context
      */
-    @Nonnull public OAuth2TokenIntrospectionResponseContext setTokenClaimsSet(
+    @Nonnull public OAuth2TokenMgmtResponseContext setTokenClaimsSet(
             @Nullable final JWTClaimsSet claims) {
         tokenClaimsSet = claims;
         
diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/OidcEventIds.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/OidcEventIds.java
index 631ff895..e4312805 100644
--- a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/OidcEventIds.java
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/OidcEventIds.java
@@ -24,7 +24,10 @@ import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
 /**
  * OpenID Connect -specific constants to use for {@link org.opensaml.profile.action.ProfileAction}
  * {@link org.opensaml.profile.context.EventContext}s.
+ * 
+ * @deprecated
  */
+ at Deprecated(since="3.1.0", forRemoval=true)
 public final class OidcEventIds {
     
     /**
@@ -76,7 +79,14 @@ public final class OidcEventIds {
      * The request uri is invalid.
      */
     @Nonnull @NotEmpty public static final String INVALID_REQUEST_URI = "InvalidRequestUri";
-
+    
+    /**
+     * The revocation attempt failed.
+     * 
+     * @since 3.1.0
+     */
+    @Nonnull @NotEmpty public static final String REVOCATION_FAILED = "RevocationFailed";
+    
     /**
      * Constructor.
      */
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ProcessTokenForIntrospection.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/AbstractProcessTokenAction.java
similarity index 84%
copy from idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ProcessTokenForIntrospection.java
copy to idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/AbstractProcessTokenAction.java
index 55a2215b..3d6b8715 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ProcessTokenForIntrospection.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/AbstractProcessTokenAction.java
@@ -38,12 +38,11 @@ import org.slf4j.LoggerFactory;
 import com.nimbusds.jose.JOSEObjectType;
 import com.nimbusds.jwt.JWTClaimsSet;
 import com.nimbusds.jwt.SignedJWT;
-import com.nimbusds.oauth2.sdk.TokenIntrospectionRequest;
 import com.nimbusds.oauth2.sdk.token.AccessToken;
 import com.nimbusds.oauth2.sdk.token.RefreshToken;
 import com.nimbusds.oauth2.sdk.token.Token;
 
-import net.shibboleth.idp.plugin.oidc.op.oauth2.messaging.context.OAuth2TokenIntrospectionResponseContext;
+import net.shibboleth.idp.plugin.oidc.op.oauth2.messaging.context.OAuth2TokenMgmtResponseContext;
 import net.shibboleth.idp.plugin.oidc.op.profile.impl.AbstractOIDCRequestAction;
 import net.shibboleth.idp.plugin.oidc.op.token.support.AccessTokenClaimsSet;
 import net.shibboleth.idp.plugin.oidc.op.token.support.RefreshTokenClaimsSet;
@@ -63,24 +62,24 @@ import net.shibboleth.utilities.java.support.security.DataSealer;
 import net.shibboleth.utilities.java.support.security.DataSealerException;
 
 /**
- * Action that processes a token for introspection by validating it and populating the resulting {@link JWTClaimsSet}
- * into an {@link OAuth2TokenIntrospectionResponseContext} placed beneath the outbound {@link MessageContext}.
+ * Action that processes a token by validating it and populating the resulting {@link JWTClaimsSet}
+ * into an {@link OAuth2TokenMgmtResponseContext} placed beneath the outbound {@link MessageContext}.
  * 
- * <p>If the token is invalid, revoked, or unintelligible, the context is not created or populated.</p>
+ * <p>If the token can't be validated, the context is not populated.</p>
+ * 
+ * @param <T> request message type
  * 
  * @since 3.1.0
  *      
- * @pre ProfileRequestContext.getInboundMessageContext().getMessage() instanceof {@link TokenIntrospectionRequest}
- * @post If the token is valid for the requester, ProfileRequestContext.getOutboundMessageContext().getSubcontext(
- *      OAuth2TokenIntrospectionResponseContext.class) != null and the context contains the token's
- *      {@link JWTClaimsSet}.
+ * @post If the token is valid for use, ProfileRequestContext.getOutboundMessageContext().getSubcontext(
+ *      OAuth2TokenMgmtResponseContext.class) != null and the context contains the token's {@link JWTClaimsSet}.
  * @event {@link EventIds#PROCEED_EVENT_ID}
  * @event {@link IdPEventIds#INVALID_PROFILE_CONFIG}
  */
-public class ProcessTokenForIntrospection extends AbstractOIDCRequestAction<TokenIntrospectionRequest> {
+public abstract class AbstractProcessTokenAction<T> extends AbstractOIDCRequestAction<T> {
 
     /** Class logger. */
-    @Nonnull private Logger log = LoggerFactory.getLogger(ProcessTokenForIntrospection.class);
+    @Nonnull private Logger log = LoggerFactory.getLogger(AbstractProcessTokenAction.class);
 
     /** Data sealer for unwrapping token. */
     @Nullable private DataSealer dataSealer;
@@ -98,7 +97,7 @@ public class ProcessTokenForIntrospection extends AbstractOIDCRequestAction<Toke
     @Nullable private SignedJWT signedJWT;
 
     /** Constructor. */
-    public ProcessTokenForIntrospection() {
+    public AbstractProcessTokenAction() {
         claimsValidatorLookupStrategy = new IssuedClaimsValidatorLookupFunction();
     }
     
@@ -150,23 +149,29 @@ public class ProcessTokenForIntrospection extends AbstractOIDCRequestAction<Toke
         return true;
     }
 
-// Checkstyle: CyclomaticComplexity OFF
+// Checkstyle: CyclomaticComplexity|MethodLength OFF
     /** {@inheritDoc} */
     @Override
     protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
         
-        log.debug("{} Token to introspect: {}", getLogPrefix(), getRequest().getToken().getValue());
+        final Token token = getToken(profileRequestContext);
+        if (token == null) {
+            log.error("{} Token missing from request", getLogPrefix());
+            return;
+        }
         
+        log.debug("{} Token to introspect: {}", getLogPrefix(), token.getValue());
+
         JWTClaimsSet tokenClaimsSet;
-        if (getRequest().getToken() instanceof AccessToken) {
-            tokenClaimsSet = parseAccessToken(getRequest().getToken());
-        } else if (getRequest().getToken() instanceof RefreshToken) {
-            tokenClaimsSet = parseRefreshToken(getRequest().getToken());
+        if (token instanceof AccessToken) {
+            tokenClaimsSet = parseAccessToken(token);
+        } else if (token instanceof RefreshToken) {
+            tokenClaimsSet = parseRefreshToken(token);
         } else {
             // No token hint, have to try both.
-            tokenClaimsSet = parseAccessToken(getRequest().getToken());
+            tokenClaimsSet = parseAccessToken(token);
             if (tokenClaimsSet == null) {
-                tokenClaimsSet = parseRefreshToken(getRequest().getToken());
+                tokenClaimsSet = parseRefreshToken(token);
             }
         }
         
@@ -218,7 +223,7 @@ public class ProcessTokenForIntrospection extends AbstractOIDCRequestAction<Toke
         
         // Populate outbound tree.
         profileRequestContext.getOutboundMessageContext().getSubcontext(
-                OAuth2TokenIntrospectionResponseContext.class).setTokenClaimsSet(tokenClaimsSet);
+                OAuth2TokenMgmtResponseContext.class).setTokenClaimsSet(tokenClaimsSet);
     }
 // Checkstyle: CyclomaticComplexity ON
 
@@ -268,4 +273,13 @@ public class ProcessTokenForIntrospection extends AbstractOIDCRequestAction<Toke
         return null;
     }
 
+    /**
+     * Get the token to process.
+     * 
+     * @param profileRequestContext current profile request context
+     * 
+     * @return the token to process
+     */
+    @Nullable protected abstract Token getToken(@Nonnull final ProfileRequestContext profileRequestContext);
+    
 }
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/FormOutboundIntrospectionResponseMessage.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/FormOutboundIntrospectionResponseMessage.java
index 46f40e99..d2beb1bc 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/FormOutboundIntrospectionResponseMessage.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/FormOutboundIntrospectionResponseMessage.java
@@ -36,7 +36,7 @@ import com.nimbusds.oauth2.sdk.id.Issuer;
 import com.nimbusds.oauth2.sdk.id.Subject;
 import com.nimbusds.oauth2.sdk.token.AccessTokenType;
 
-import net.shibboleth.idp.plugin.oidc.op.oauth2.messaging.context.OAuth2TokenIntrospectionResponseContext;
+import net.shibboleth.idp.plugin.oidc.op.oauth2.messaging.context.OAuth2TokenMgmtResponseContext;
 import net.shibboleth.idp.plugin.oidc.op.profile.impl.AbstractOIDCRequestAction;
 import net.shibboleth.idp.plugin.oidc.op.token.support.TokenClaimsSet;
 
@@ -52,18 +52,18 @@ public class FormOutboundIntrospectionResponseMessage extends AbstractOIDCReques
     /** {@inheritDoc} */
     @Override protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
         
-        final OAuth2TokenIntrospectionResponseContext ctx =
+        final OAuth2TokenMgmtResponseContext ctx =
                 profileRequestContext.getOutboundMessageContext().getSubcontext(
-                        OAuth2TokenIntrospectionResponseContext.class);
+                        OAuth2TokenMgmtResponseContext.class);
         
-        if (ctx == null || ctx.getTokenClaimSet() == null) {
+        if (ctx == null || ctx.getTokenClaimsSet() == null) {
             log.debug("{} Introspection of token failed, token was not valid", getLogPrefix());
             profileRequestContext.getOutboundMessageContext().setMessage(
                     new TokenIntrospectionSuccessResponse.Builder(false).build());
             return;
         }
 
-        final JWTClaimsSet tokenClaimsSet = ctx.getTokenClaimSet();
+        final JWTClaimsSet tokenClaimsSet = ctx.getTokenClaimsSet();
 
         try {
             String clientID = tokenClaimsSet.getStringClaim(TokenClaimsSet.KEY_CLIENTID);
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/FormOutboundRevokeTokenResponseMessage.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/FormOutboundRevokeTokenResponseMessage.java
index 532a0cdc..a34e4f24 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/FormOutboundRevokeTokenResponseMessage.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/FormOutboundRevokeTokenResponseMessage.java
@@ -21,18 +21,21 @@ import javax.annotation.Nonnull;
 
 import org.opensaml.profile.context.ProfileRequestContext;
 
+import com.nimbusds.oauth2.sdk.TokenRevocationRequest;
+
 import net.shibboleth.idp.plugin.oidc.op.oauth2.messaging.impl.OAuth2RevocationSuccessResponse;
-import net.shibboleth.idp.plugin.oidc.op.profile.impl.AbstractOIDCResponseAction;
+import net.shibboleth.idp.plugin.oidc.op.profile.impl.AbstractOIDCRequestAction;
 
 /**
  * Action that forms outbound token revocation success message. Formed message is set to
  * {@link ProfileRequestContext#getOutboundMessageContext()}.
  */
-public class FormOutboundRevokeTokenResponseMessage extends AbstractOIDCResponseAction {
+public class FormOutboundRevokeTokenResponseMessage extends AbstractOIDCRequestAction<TokenRevocationRequest> {
 
     /** {@inheritDoc} */
     @Override
     protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
         profileRequestContext.getOutboundMessageContext().setMessage(new OAuth2RevocationSuccessResponse());
     }
+
 }
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/InitializeOutboundTokenIntrospectionResponseMessageContext.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/InitializeOutboundTokenMgmtResponseMessageContext.java
similarity index 80%
rename from idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/InitializeOutboundTokenIntrospectionResponseMessageContext.java
rename to idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/InitializeOutboundTokenMgmtResponseMessageContext.java
index 0776a1a9..46cd96b1 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/InitializeOutboundTokenIntrospectionResponseMessageContext.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/InitializeOutboundTokenMgmtResponseMessageContext.java
@@ -20,21 +20,20 @@ package net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl;
 import org.opensaml.messaging.context.MessageContext;
 import org.opensaml.profile.context.ProfileRequestContext;
 
-import net.shibboleth.idp.plugin.oidc.op.oauth2.messaging.context.OAuth2TokenIntrospectionResponseContext;
+import net.shibboleth.idp.plugin.oidc.op.oauth2.messaging.context.OAuth2TokenMgmtResponseContext;
 import net.shibboleth.idp.plugin.oidc.op.profile.impl.AbstractInitializeOutboundResponseMessageContext;
 
 /**
- * Action that adds an outbound {@link MessageContext} and related contexts to the {@link ProfileRequestContext}
- * not knowing the relying party yet.
+ * Action that adds an outbound {@link MessageContext} and related contexts to the {@link ProfileRequestContext}.
  *
  * @event {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID}
  */
-public class InitializeOutboundTokenIntrospectionResponseMessageContext
+public class InitializeOutboundTokenMgmtResponseMessageContext
         extends AbstractInitializeOutboundResponseMessageContext {
 
     /** Constructor. */
-    public InitializeOutboundTokenIntrospectionResponseMessageContext() {
-        setContextType(OAuth2TokenIntrospectionResponseContext.class);
+    public InitializeOutboundTokenMgmtResponseMessageContext() {
+        setContextType(OAuth2TokenMgmtResponseContext.class);
     }
 
 }
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ProcessTokenForIntrospection.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ProcessTokenForIntrospection.java
index 55a2215b..b2e65aa0 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ProcessTokenForIntrospection.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ProcessTokenForIntrospection.java
@@ -17,255 +17,24 @@
 
 package net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl;
 
-import java.text.ParseException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.function.Function;
-
 import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
 
-import org.opensaml.messaging.context.MessageContext;
-import org.opensaml.profile.action.EventIds;
 import org.opensaml.profile.context.ProfileRequestContext;
-import org.opensaml.security.credential.Credential;
-import org.opensaml.security.credential.CredentialResolver;
-import org.opensaml.security.credential.UsageType;
-import org.opensaml.security.criteria.UsageCriterion;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
-import com.nimbusds.jose.JOSEObjectType;
-import com.nimbusds.jwt.JWTClaimsSet;
-import com.nimbusds.jwt.SignedJWT;
 import com.nimbusds.oauth2.sdk.TokenIntrospectionRequest;
-import com.nimbusds.oauth2.sdk.token.AccessToken;
-import com.nimbusds.oauth2.sdk.token.RefreshToken;
 import com.nimbusds.oauth2.sdk.token.Token;
 
-import net.shibboleth.idp.plugin.oidc.op.oauth2.messaging.context.OAuth2TokenIntrospectionResponseContext;
-import net.shibboleth.idp.plugin.oidc.op.profile.impl.AbstractOIDCRequestAction;
-import net.shibboleth.idp.plugin.oidc.op.token.support.AccessTokenClaimsSet;
-import net.shibboleth.idp.plugin.oidc.op.token.support.RefreshTokenClaimsSet;
-import net.shibboleth.idp.profile.ActionSupport;
-import net.shibboleth.idp.profile.IdPEventIds;
-import net.shibboleth.oidc.jwt.claims.ClaimsValidator;
-import net.shibboleth.oidc.jwt.claims.JWTValidationException;
-import net.shibboleth.oidc.profile.config.navigate.IssuedClaimsValidatorLookupFunction;
-import net.shibboleth.oidc.profile.core.OidcEventIds;
-import net.shibboleth.oidc.security.impl.JWTSignatureValidationUtil;
-import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
-import net.shibboleth.utilities.java.support.component.ComponentSupport;
-import net.shibboleth.utilities.java.support.logic.Constraint;
-import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
-import net.shibboleth.utilities.java.support.resolver.ResolverException;
-import net.shibboleth.utilities.java.support.security.DataSealer;
-import net.shibboleth.utilities.java.support.security.DataSealerException;
-
 /**
- * Action that processes a token for introspection by validating it and populating the resulting {@link JWTClaimsSet}
- * into an {@link OAuth2TokenIntrospectionResponseContext} placed beneath the outbound {@link MessageContext}.
- * 
- * <p>If the token is invalid, revoked, or unintelligible, the context is not created or populated.</p>
+ * Action that processes a token for introspection.
  * 
  * @since 3.1.0
- *      
- * @pre ProfileRequestContext.getInboundMessageContext().getMessage() instanceof {@link TokenIntrospectionRequest}
- * @post If the token is valid for the requester, ProfileRequestContext.getOutboundMessageContext().getSubcontext(
- *      OAuth2TokenIntrospectionResponseContext.class) != null and the context contains the token's
- *      {@link JWTClaimsSet}.
- * @event {@link EventIds#PROCEED_EVENT_ID}
- * @event {@link IdPEventIds#INVALID_PROFILE_CONFIG}
  */
-public class ProcessTokenForIntrospection extends AbstractOIDCRequestAction<TokenIntrospectionRequest> {
+public class ProcessTokenForIntrospection extends AbstractProcessTokenAction<TokenIntrospectionRequest> {
 
-    /** Class logger. */
-    @Nonnull private Logger log = LoggerFactory.getLogger(ProcessTokenForIntrospection.class);
-
-    /** Data sealer for unwrapping token. */
-    @Nullable private DataSealer dataSealer;
-    
-    /** Lookup strategy for claims validator. */
-    @Nonnull private Function<ProfileRequestContext,ClaimsValidator> claimsValidatorLookupStrategy;
-    
-    /** The claims validator to use. */
-    @Nullable private ClaimsValidator claimsValidator;
-    
-    /** Source of signing keys. */
-    @Nullable private CredentialResolver credentialResolver;
-    
-    /** Copy of signed JWT for non-opaque access tokens. */
-    @Nullable private SignedJWT signedJWT;
-
-    /** Constructor. */
-    public ProcessTokenForIntrospection() {
-        claimsValidatorLookupStrategy = new IssuedClaimsValidatorLookupFunction();
-    }
-    
-    /**
-     * Set the data sealer instance to use.
-     * 
-     * @param sealer data sealer to use
-     */
-    public void setDataSealer(@Nullable final DataSealer sealer) {
-        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-        dataSealer = sealer;
-    }
-    
-    /**
-     * Set the claims validator lookup strategy.
-     * 
-     * @param strategy lookup strategy
-     */
-    public void setClaimsValidatorLookupStrategy(
-            @Nonnull final Function<ProfileRequestContext,ClaimsValidator> strategy) {
-        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-        claimsValidatorLookupStrategy = Constraint.isNotNull(strategy, "Lookup strategy cannot be null");
-    }
-    
-    /**
-     * Set the source of signing keys to use for JWT signature verification.
-     * 
-     * @param resolver signing key resolver
-     */
-    public void setCredentialResolver(@Nullable final CredentialResolver resolver) {
-        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-        credentialResolver = resolver;
-    }
-        
     /** {@inheritDoc} */
     @Override
-    protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
-        if (!super.doPreExecute(profileRequestContext)) {
-            return false;
-        }
-        
-        claimsValidator = claimsValidatorLookupStrategy.apply(profileRequestContext);
-        if (claimsValidator == null) {
-            log.error("{} Unable to obtain ClaimsValidator to apply", getLogPrefix());
-            ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_PROFILE_CONFIG);
-            return false;
-        }
-        
-        return true;
-    }
-
-// Checkstyle: CyclomaticComplexity OFF
-    /** {@inheritDoc} */
-    @Override
-    protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
-        
-        log.debug("{} Token to introspect: {}", getLogPrefix(), getRequest().getToken().getValue());
-        
-        JWTClaimsSet tokenClaimsSet;
-        if (getRequest().getToken() instanceof AccessToken) {
-            tokenClaimsSet = parseAccessToken(getRequest().getToken());
-        } else if (getRequest().getToken() instanceof RefreshToken) {
-            tokenClaimsSet = parseRefreshToken(getRequest().getToken());
-        } else {
-            // No token hint, have to try both.
-            tokenClaimsSet = parseAccessToken(getRequest().getToken());
-            if (tokenClaimsSet == null) {
-                tokenClaimsSet = parseRefreshToken(getRequest().getToken());
-            }
-        }
-        
-        if (tokenClaimsSet == null) {
-            log.warn("{} Unable to parse/decode token for introspection", getLogPrefix());
-            return;
-        }
-
-        if (signedJWT != null) {
-            // Check typ header.
-            final JOSEObjectType typ = signedJWT.getHeader().getType();
-            if (typ == null || !"at+jwt".equals(typ.getType())) {
-                log.warn("{} Missing or invalid token type: {}", getLogPrefix(), typ != null ? typ.getType() : "null");
-                return;
-            }
-            
-            if (credentialResolver == null) {
-                log.error("{} No CredentialResolver available, can't verify JWT signature", getLogPrefix());
-                return;
-            }
-            
-            log.debug("{} Checking JWT signature", getLogPrefix());
-            final Collection<Credential> credList = new ArrayList<>();
-            final CriteriaSet criteriaSet = new CriteriaSet(new UsageCriterion(UsageType.SIGNING));
-            try {
-                final Iterable<Credential> creds = credentialResolver.resolve(criteriaSet);
-                if (creds != null) {
-                    creds.forEach(credList::add);
-                }
-            } catch (final ResolverException e) {
-                log.error("{} Failure resolving signing credentials, can't verify JWT signature", getLogPrefix(), e);
-                return;
-            }
-            final String errorEventId = JWTSignatureValidationUtil.validateSignatureEx(credList, signedJWT,
-                    OidcEventIds.INVALID_GRANT);
-            if (errorEventId != null) {
-                log.warn("{} Signature on token ID '{}' invalid", getLogPrefix(), tokenClaimsSet.getJWTID());
-                return;
-            }
-        }
-
-        log.debug("{} Validating parsed/decoded claims set: {}", getLogPrefix(), tokenClaimsSet.toString());
-        try {
-            claimsValidator.validate(tokenClaimsSet, profileRequestContext);
-        } catch (final JWTValidationException e) {
-            log.warn("{} Claims validation failed, token is invalid: {}", getLogPrefix(), e.getMessage());
-            return;
-        }
-        
-        // Populate outbound tree.
-        profileRequestContext.getOutboundMessageContext().getSubcontext(
-                OAuth2TokenIntrospectionResponseContext.class).setTokenClaimsSet(tokenClaimsSet);
-    }
-// Checkstyle: CyclomaticComplexity ON
-
-    /**
-     * Attempt to parse token.
-     * 
-     * @param token the token
-     * 
-     * @return parsed claim set or null
-     */
-    @Nullable protected JWTClaimsSet parseAccessToken(@Nonnull @NotEmpty final Token token) {
-        
-        // Try parsing as a JWT.
-        try {
-            signedJWT = SignedJWT.parse(token.getValue());
-            return signedJWT.getJWTClaimsSet();
-        } catch (final ParseException e1) {
-            
-        }
-
-        // Fall back to opaque.
-        try {
-            return AccessTokenClaimsSet.parse(token.getValue(), dataSealer).getClaimsSet();
-        } catch (final DataSealerException | ParseException e) {
-            
-        }
-        
-        return null;
-    }
-
-    /**
-     * Attempt to parse refresh token.
-     * 
-     * @param token the token
-     * 
-     * @return parsed claim set or null
-     */
-    @Nullable protected JWTClaimsSet parseRefreshToken(@Nonnull @NotEmpty final Token token) {
-
-        // All refresh tokens are opaque.
-        try {
-            return RefreshTokenClaimsSet.parse(token.getValue(), dataSealer).getClaimsSet();
-        } catch (final DataSealerException | ParseException e) {
-            
-        }
-        
-        return null;
+    protected Token getToken(@Nonnull final ProfileRequestContext profileRequestContext) {
+        return getRequest().getToken();
     }
 
 }
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/InitializeOutboundRevokeTokenResponseMessageContext.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ProcessTokenForRevocation.java
similarity index 62%
rename from idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/InitializeOutboundRevokeTokenResponseMessageContext.java
rename to idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ProcessTokenForRevocation.java
index 09aafc11..091edc3a 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/InitializeOutboundRevokeTokenResponseMessageContext.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ProcessTokenForRevocation.java
@@ -17,20 +17,24 @@
 
 package net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl;
 
-import org.opensaml.messaging.context.MessageContext;
+import javax.annotation.Nonnull;
+
 import org.opensaml.profile.context.ProfileRequestContext;
 
-import net.shibboleth.idp.plugin.oidc.op.profile.impl.AbstractInitializeOutboundResponseMessageContext;
+import com.nimbusds.oauth2.sdk.TokenRevocationRequest;
+import com.nimbusds.oauth2.sdk.token.Token;
 
 /**
- * Action that adds an outbound {@link MessageContext} and related OIDC contexts to the {@link ProfileRequestContext}
- * not knowing the relying party yet.
+ * Action that processes a token for revocation.
  * 
- * TODO: This class can be eliminated now that generics are gone.
- *
- * @event {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID}
+ * @since 3.1.0
  */
-public class InitializeOutboundRevokeTokenResponseMessageContext
-        extends AbstractInitializeOutboundResponseMessageContext {
+public class ProcessTokenForRevocation extends AbstractProcessTokenAction<TokenRevocationRequest> {
+
+    /** {@inheritDoc} */
+    @Override
+    protected Token getToken(@Nonnull final ProfileRequestContext profileRequestContext) {
+        return getRequest().getToken();
+    }
 
 }
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/RevokeToken.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/RevokeToken.java
index dead3564..91e7da31 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/RevokeToken.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/RevokeToken.java
@@ -17,55 +17,39 @@
 
 package net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl;
 
-import java.text.ParseException;
 import javax.annotation.Nonnull;
 
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.action.EventIds;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.opensaml.storage.RevocationCache;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import com.nimbusds.oauth2.sdk.TokenRevocationRequest;
 
-import net.shibboleth.idp.plugin.oidc.op.profile.impl.AbstractOIDCRequestAction;
+import net.shibboleth.idp.plugin.oidc.op.oauth2.messaging.context.OAuth2TokenMgmtResponseContext;
 import net.shibboleth.idp.plugin.oidc.op.storage.RevocationCacheContexts;
-import net.shibboleth.idp.plugin.oidc.op.token.support.AccessTokenClaimsSet;
-import net.shibboleth.idp.plugin.oidc.op.token.support.RefreshTokenClaimsSet;
-import net.shibboleth.idp.plugin.oidc.op.token.support.TokenClaimsSet;
+import net.shibboleth.idp.profile.AbstractProfileAction;
+import net.shibboleth.oidc.profile.core.OidcEventIds;
 import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
 import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
 import net.shibboleth.utilities.java.support.component.ComponentSupport;
 import net.shibboleth.utilities.java.support.logic.Constraint;
-import net.shibboleth.utilities.java.support.security.DataSealer;
-import net.shibboleth.utilities.java.support.security.DataSealerException;
 
 /**
- * Action revokes all tokens based on authorization grant of the token to be revoked. If token is access token or
- * refresh token, the id of the authorization code they are derived from is marked as revoked and so invalidating all
+ * Action revokes a token. If the token is was derived from an authorization code, the id of the
+ * authorization code they are derived from is marked as revoked and so invalidating all
  * tokens based on it.
  * 
- * If the token to be revoked is not decodable or the revocation fails, the actions still returns success status.
+ * @event {@link EventIds#PROCEED_EVENT_ID}
+ * @event {@link OidcEventIds#REVOCATION_FAILED}
  */
-public class RevokeToken extends AbstractOIDCRequestAction<TokenRevocationRequest> {
+public class RevokeToken extends AbstractProfileAction {
 
     /** Class logger. */
-    @Nonnull
-    private Logger log = LoggerFactory.getLogger(RevokeToken.class);
-
-    /** Data sealer for unwrapping token. */
-    @NonnullAfterInit private DataSealer dataSealer;
+    @Nonnull private Logger log = LoggerFactory.getLogger(RevokeToken.class);
 
     /** Message revocation cache instance to use. */
     @NonnullAfterInit private RevocationCache revocationCache;
-
-    /**
-     * Set the data sealer instance to use.
-     * 
-     * @param sealer data sealer to use
-     */
-    public void setDataSealer(@Nonnull final DataSealer sealer) {
-        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-        dataSealer = Constraint.isNotNull(sealer, "DataSealer cannot be null");
-    }
     
     /**
      * Set the revocation cache instance to use.
@@ -82,7 +66,7 @@ public class RevokeToken extends AbstractOIDCRequestAction<TokenRevocationReques
     protected void doInitialize() throws ComponentInitializationException {
         super.doInitialize();
         
-        if (revocationCache == null || dataSealer == null) {
+        if (revocationCache == null) {
             throw new ComponentInitializationException("RevocationCache and DataSealer cannot be null");
         }
     }
@@ -90,30 +74,26 @@ public class RevokeToken extends AbstractOIDCRequestAction<TokenRevocationReques
     /** {@inheritDoc} */
     @Override
     protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
-        TokenClaimsSet tokenClaimsSet = null;
-        log.debug("{} token to revoke {}", getLogPrefix(), getRequest().getToken().getValue());
-        try {
-            tokenClaimsSet = AccessTokenClaimsSet.parse(getRequest().getToken().getValue(), dataSealer);
-            log.debug("{} access token unwrapped {}", getLogPrefix(), tokenClaimsSet.serialize());
-        } catch (final DataSealerException | ParseException e) {
-            log.debug("{} token to revoke is not valid access token", getLogPrefix(), e);
-        }
-        if (tokenClaimsSet == null) {
-            try {
-                tokenClaimsSet = RefreshTokenClaimsSet.parse(getRequest().getToken().getValue(), dataSealer);
-                log.debug("{} refresh token unwrapped {}", getLogPrefix(), tokenClaimsSet.serialize());
-            } catch (final DataSealerException | ParseException e) {
-                log.debug("{} token to revoke is not valid refresh token", getLogPrefix());
-            }
+        
+        final OAuth2TokenMgmtResponseContext ctx = profileRequestContext.getOutboundMessageContext().getSubcontext(
+                OAuth2TokenMgmtResponseContext.class);
+        if (ctx == null || ctx.getTokenClaimsSet() == null) {
+            log.debug("{} No token validated for revocation, assumed to be invalid", getLogPrefix());
+            return;
         }
-        if (tokenClaimsSet == null) {
-            log.debug("{} unable to decode token to revoke, nothing to do", getLogPrefix());
+        
+        final String id = ctx.getTokenClaimsSet().getJWTID(); 
+        if (id == null) {
+            log.error("{} No ID found in token claims set (this should be impossible)", getLogPrefix());
             return;
         }
-        if (revocationCache.revoke(RevocationCacheContexts.AUTHORIZATION_CODE, tokenClaimsSet.getID())) {
-            log.debug("{} revoked all tokens based on authorize code {}", getLogPrefix(), tokenClaimsSet.getID());
+        
+        if (revocationCache.revoke(RevocationCacheContexts.AUTHORIZATION_CODE, id)) {
+            log.debug("{} Revoked all tokens based on ID '{}'", getLogPrefix(), id);
         } else {
-            log.warn("{} failed to revoke tokens based on authorize code {}", getLogPrefix(), tokenClaimsSet.getID());
+            log.warn("{} Failed to revoke tokens based on ID '{}'", getLogPrefix(), id);
+            ActionSupport.buildEvent(profileRequestContext, OidcEventIds.REVOCATION_FAILED);
         }
     }
+
 }
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AbstractOIDCResponseAction.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AbstractOIDCResponseAction.java
index cd211031..8ef562a8 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AbstractOIDCResponseAction.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AbstractOIDCResponseAction.java
@@ -80,14 +80,14 @@ public abstract class AbstractOIDCResponseAction extends AbstractProfileAction {
             ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
             return false;
         }
-        oidcResponseContext = outboundMessageCtx.getSubcontext(OIDCAuthenticationResponseContext.class, false);
+        oidcResponseContext = outboundMessageCtx.getSubcontext(OIDCAuthenticationResponseContext.class);
         if (oidcResponseContext == null) {
             log.error("{} No oidc response context", getLogPrefix());
             ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
             return false;
         }
         oidcMetadataContext =
-                profileRequestContext.getInboundMessageContext().getSubcontext(OIDCMetadataContext.class, false);
+                profileRequestContext.getInboundMessageContext().getSubcontext(OIDCMetadataContext.class);
         if (oidcMetadataContext == null) {
             log.error("{} No metadata found for relying party", getLogPrefix());
             ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/introspection/introspection-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/introspection/introspection-beans.xml
index 4fb590f2..67ada2bc 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/introspection/introspection-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/introspection/introspection-beans.xml
@@ -24,7 +24,7 @@
         scope="prototype" />
 
     <bean id="InitializeOutboundMessageContext"
-        class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.InitializeOutboundTokenIntrospectionResponseMessageContext"
+        class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.InitializeOutboundTokenMgmtResponseMessageContext"
         scope="prototype" />
 
     <bean id="ProcessTokenForIntrospection"
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/introspection/introspection-flow.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/introspection/introspection-flow.xml
index dbdd7c1d..5bd77f7e 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/introspection/introspection-flow.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/introspection/introspection-flow.xml
@@ -9,6 +9,7 @@
         <evaluate expression="FlowStartPopulateAuditContext" />
         <evaluate expression="InitializeOutboundMessageContext" />
         <evaluate expression="'proceed'" />
+        
         <transition on="proceed" to="DecodeMessage" />
     </action-state>
 
@@ -16,6 +17,7 @@
         <evaluate expression="DecodeMessage" />
         <evaluate expression="PostDecodePopulateAuditContext" />
         <evaluate expression="'proceed'" />
+        
         <!-- DoMetadataLookup is expected to proceed to SelectConfiguration -->
         <transition on="proceed" to="DoMetadataLookup" />
     </action-state>
@@ -25,6 +27,7 @@
         <evaluate expression="SelectProfileConfiguration" />
         <evaluate expression="PostLookupPopulateAuditContext" />
         <evaluate expression="'proceed'" />
+        
         <transition on="proceed" to="AuthenticationSetup" />
     </action-state>
     
@@ -33,6 +36,7 @@
     <action-state id="ResumeAfterAuthentication">
         <evaluate expression="ProcessTokenForIntrospection" />
         <evaluate expression="'proceed'" />
+        
         <transition on="proceed" to="BuildResponseMessage" />
     </action-state>
     
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/revocation/revocation-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/revocation/revocation-beans.xml
index ed8a563a..334b5849 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/revocation/revocation-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/revocation/revocation-beans.xml
@@ -24,11 +24,18 @@
         scope="prototype" />
 
     <bean id="InitializeOutboundMessageContext"
-        class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.InitializeOutboundRevokeTokenResponseMessageContext"
+        class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.InitializeOutboundTokenMgmtResponseMessageContext"
         scope="prototype" />
 
-    <bean id="RevokeToken" class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.RevokeToken" scope="prototype"
+    <bean id="ProcessTokenForRevocation"
+        class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.ProcessTokenForRevocation" scope="prototype"
         p:dataSealer-ref="#{'%{idp.oidc.tokenSealer:shibboleth.oidc.TokenSealer}'.trim()}"
+        p:credentialResolver-ref="SigningCredentialsResolver" />
+
+    <bean id="SigningCredentialsResolver" class="net.shibboleth.idp.relyingparty.impl.SigningCredentialsResolver" 
+        c:_0-ref="shibboleth.RelyingPartyResolverService" />
+
+    <bean id="RevokeToken" class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.RevokeToken" scope="prototype"
         p:revocationCache-ref="shibboleth.oidc.RevocationCache" />
 
     <bean id="FormOutboundMessage"
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/revocation/revocation-flow.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/revocation/revocation-flow.xml
index 775360af..5ad69304 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/revocation/revocation-flow.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/revocation/revocation-flow.xml
@@ -9,6 +9,7 @@
         <evaluate expression="FlowStartPopulateAuditContext" />
         <evaluate expression="InitializeOutboundMessageContext" />
         <evaluate expression="'proceed'" />
+        
         <transition on="proceed" to="DecodeMessage" />
     </action-state>
 
@@ -16,6 +17,7 @@
         <evaluate expression="DecodeMessage" />
         <evaluate expression="PostDecodePopulateAuditContext" />
         <evaluate expression="'proceed'" />
+        
         <!-- DoMetadataLookup is expected to proceed to SelectConfiguration -->
         <transition on="proceed" to="DoMetadataLookup" />
     </action-state>
@@ -25,19 +27,17 @@
         <evaluate expression="SelectProfileConfiguration" />
         <evaluate expression="PostLookupPopulateAuditContext" />
         <evaluate expression="'proceed'" />
+        
         <transition on="proceed" to="AuthenticationSetup" />
     </action-state>
 
     <!-- Authentication subflow happens here. -->
 
     <action-state id="ResumeAfterAuthentication">
-        <evaluate expression="'proceed'" />
-        <transition on="proceed" to="RevokeToken" />
-    </action-state>
-
-    <action-state id="RevokeToken">
+        <evaluate expression="ProcessTokenForRevocation" />
         <evaluate expression="RevokeToken" />
         <evaluate expression="'proceed'" />
+        
         <transition on="proceed" to="BuildResponseMessage" />
     </action-state>
     
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/abstract-api/oidc-abstract-api-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/abstract-api/oidc-abstract-api-beans.xml
index 24e4a783..e2fc9f96 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/abstract-api/oidc-abstract-api-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/abstract-api/oidc-abstract-api-beans.xml
@@ -36,12 +36,15 @@
         <entry key="#{T(org.opensaml.profile.action.EventIds).ACCESS_DENIED}"
             value="#{T(com.nimbusds.oauth2.sdk.OAuth2Error).ACCESS_DENIED}" />
             
-        <entry key="#{T(net.shibboleth.idp.plugin.oidc.op.profile.OidcEventIds).INVALID_GRANT_TYPE}"
+        <entry key="#{T(net.shibboleth.oidc.profile.core.OidcEventIds).INVALID_GRANT_TYPE}"
             value="#{T(com.nimbusds.oauth2.sdk.OAuth2Error).UNAUTHORIZED_CLIENT}" />
-        <entry key="#{T(net.shibboleth.idp.plugin.oidc.op.profile.OidcEventIds).INVALID_GRANT}"
+        <entry key="#{T(net.shibboleth.oidc.profile.core.OidcEventIds).INVALID_GRANT}"
             value="#{T(com.nimbusds.oauth2.sdk.OAuth2Error).INVALID_GRANT}" />
-        <entry key="#{T(net.shibboleth.idp.plugin.oidc.op.profile.OidcEventIds).INVALID_REDIRECT_URI}"
+        <entry key="#{T(net.shibboleth.oidc.profile.core.OidcEventIds).INVALID_REDIRECT_URI}"
             value="#{T(com.nimbusds.oauth2.sdk.OAuth2Error).INVALID_REQUEST}" />
+            
+        <entry key="#{T(net.shibboleth.oidc.profile.core.OidcEventIds).REVOCATION_FAILED}"
+            value="#{T(com.nimbusds.oauth2.sdk.OAuth2Error).SERVER_ERROR}" />
     </util:map>
 
 </beans>
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml
index d14da7d0..46762697 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml
@@ -66,13 +66,13 @@
         class="net.shibboleth.oidc.profile.oauth2.config.OAuth2TokenIntrospectionConfiguration"
         p:issuer-ref="issuer"
         p:tokenEndpointAuthMethods="%{idp.oidc.dynreg.tokenEndpointAuthMethods:client_secret_basic,client_secret_post,client_secret_jwt,private_key_jwt}"
-        p:claimsValidator-ref="DefaultIssuedJWTClaimsValidator" />
+        p:claimsValidator-ref="DefaultIntrospectionJWTClaimsValidator" />
 
     <bean id="OAUTH2.Revocation" parent="AbstractOIDCProfile" lazy-init="true"
         class="net.shibboleth.oidc.profile.oauth2.config.OAuth2TokenRevocationConfiguration"
         p:issuer-ref="issuer"
         p:tokenEndpointAuthMethods="%{idp.oidc.dynreg.tokenEndpointAuthMethods:client_secret_basic,client_secret_post,client_secret_jwt,private_key_jwt}"
-        p:claimsValidator-ref="DefaultIssuedJWTClaimsValidator" />        
+        p:claimsValidator-ref="DefaultRevocationJWTClaimsValidator" />        
 
     <!-- Metadata-driven variants. -->
     
@@ -117,11 +117,6 @@
         <property name="issuerLookupStrategy">
             <bean parent="shibboleth.MDDrivenStringProperty" p:propertyName="issuer" p:defaultValue-ref="issuer" />
         </property>
-        <property name="issuedClaimsValidatorLookupStrategy">
-            <bean parent="shibboleth.MDDrivenBeanProperty" p:propertyName="issuedClaimsValidator"
-                p:propertyType="#{T(net.shibboleth.oidc.jwt.claims.ClaimsValidator)}"
-                p:defaultValue-ref="DefaultIssuedJWTClaimsValidator" />
-        </property>
     </bean>
         
     <bean id="AbstractMDDrivenOIDCFlowAwareProfile" parent="AbstractMDDrivenOAuthClientAuthenticatableProfile" abstract="true">
@@ -364,6 +359,11 @@
                 </property>
             </bean>
         </property>
+        <property name="issuedClaimsValidatorLookupStrategy">
+            <bean parent="shibboleth.MDDrivenBeanProperty" p:propertyName="issuedClaimsValidator"
+                p:propertyType="#{T(net.shibboleth.oidc.jwt.claims.ClaimsValidator)}"
+                p:defaultValue-ref="DefaultRevocationJWTClaimsValidator" />
+        </property>
     </bean>
 
     <bean id="OAUTH2.Introspection.MDDriven" parent="AbstractMDDrivenOAuthTokenValidatingProfile" lazy-init="true"
@@ -378,6 +378,11 @@
                 </property>
             </bean>
         </property>
+        <property name="issuedClaimsValidatorLookupStrategy">
+            <bean parent="shibboleth.MDDrivenBeanProperty" p:propertyName="issuedClaimsValidator"
+                p:propertyType="#{T(net.shibboleth.oidc.jwt.claims.ClaimsValidator)}"
+                p:defaultValue-ref="DefaultIntrospectionJWTClaimsValidator" />
+        </property>
     </bean>
 
     <!-- Default client-auth JWT validation wiring. -->
@@ -437,9 +442,13 @@
 
     <!-- Default issued JWT validation wiring (for introspection/revocation). -->
 
-    <bean id="DefaultIssuedJWTClaimsValidator"
+    <bean id="DefaultIntrospectionJWTClaimsValidator"
         class="net.shibboleth.oidc.security.jwt.claims.impl.ChainingJWTClaimsValidator"
-        p:claimValidators-ref="IssuedClaimsValidators" />
+        p:claimValidators-ref="IntrospectionClaimsValidators" />
+
+    <bean id="DefaultRevocationJWTClaimsValidator"
+        class="net.shibboleth.oidc.security.jwt.claims.impl.ChainingJWTClaimsValidator"
+        p:claimValidators-ref="IntrospectionClaimsValidators" />
 
     <bean id="SelfIssuedClaimsValidator"
             class="net.shibboleth.oidc.security.jwt.claims.impl.ExactMatchClaimsValidator"
@@ -451,6 +460,10 @@
         </property>
     </bean>
 
+    <bean id="RequiredClaimsValidator"
+        class="net.shibboleth.oidc.security.jwt.claims.impl.RequiredClaimsValidator"
+        p:requiredClaims="jti" />
+
     <bean id="ClientIDClaimsValidator"
         class="net.shibboleth.oidc.security.jwt.claims.impl.ExactMatchClaimsValidator"
         p:claimName="client_id" p:valueToMatchLookupStrategy-ref="ClientIDFromOIDCMetadataContextLookupFunction" />
@@ -468,7 +481,8 @@
         p:revocationCache-ref="shibboleth.oidc.RevocationCache"
         p:context="#{T(net.shibboleth.idp.plugin.oidc.op.storage.RevocationCacheContexts).AUTHORIZATION_CODE}" />
 
-    <util:list id="IssuedClaimsValidators" value-type="net.shibboleth.oidc.jwt.claims.ClaimsValidator">
+    <util:list id="IntrospectionClaimsValidators" value-type="net.shibboleth.oidc.jwt.claims.ClaimsValidator">
+        <ref bean="RequiredClaimsValidator" />
         <ref bean="ExpiryClaimsValidator" />
         <ref bean="NotBeforeClaimsValidator" />
         <ref bean="SelfIssuedClaimsValidator" />
@@ -485,6 +499,22 @@
         <ref bean="JWTIDRevocationClaimsValidator" />
     </util:list>
 
+    <util:list id="RevocationClaimsValidators" value-type="net.shibboleth.oidc.jwt.claims.ClaimsValidator">
+        <ref bean="RequiredClaimsValidator" />
+        <ref bean="ExpiryClaimsValidator" />
+        <ref bean="SelfIssuedClaimsValidator" />
+        <!-- For issued tokens, ensure that the requester is either the client_id or the audience. -->
+        <bean class="net.shibboleth.oidc.security.jwt.claims.impl.ChainingJWTClaimsValidator" p:requireAll="false">
+            <property name="claimValidators">
+                <list value-type="net.shibboleth.oidc.jwt.claims.ClaimsValidator">
+                    <ref bean="ClientIDClaimsValidator" />
+                    <ref bean="LegacyClientIDClaimsValidator" />
+                    <ref bean="ClientIDInAudienceClaimsValidator" />
+                </list>
+            </property>
+        </bean>
+    </util:list>
+
     <!--
     Auto-wiring exposers for credentials to get them loaded into the IdP's relying party config resolver.
     The qualifiers control which auto-wiring point is used.
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/RevokeTokenTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/RevokeTokenTest.java
index 82b7eb76..6c4f4fc1 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/RevokeTokenTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/RevokeTokenTest.java
@@ -17,11 +17,9 @@
 
 package net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl;
 
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.security.NoSuchAlgorithmException;
 import java.time.Duration;
 
+import org.opensaml.profile.context.ProfileRequestContext;
 import org.opensaml.storage.RevocationCache;
 import org.opensaml.storage.impl.MemoryStorageService;
 import org.springframework.webflow.execution.RequestContext;
@@ -30,19 +28,15 @@ import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
-import com.nimbusds.oauth2.sdk.TokenRevocationRequest;
-import com.nimbusds.oauth2.sdk.id.ClientID;
-import com.nimbusds.oauth2.sdk.token.BearerAccessToken;
-
+import net.shibboleth.idp.plugin.oidc.op.oauth2.messaging.context.OAuth2TokenMgmtResponseContext;
 import net.shibboleth.idp.plugin.oidc.op.storage.RevocationCacheContexts;
 import net.shibboleth.idp.plugin.oidc.op.token.support.AccessTokenClaimsSet;
 import net.shibboleth.idp.plugin.oidc.op.token.support.AuthorizeCodeClaimsSet;
 import net.shibboleth.idp.plugin.oidc.op.token.support.RefreshTokenClaimsSet;
 import net.shibboleth.idp.plugin.oidc.op.token.support.testing.BaseTokenClaimsSetTest;
+import net.shibboleth.idp.profile.context.navigate.WebflowRequestContextProfileRequestContextLookup;
 import net.shibboleth.idp.profile.testing.ActionTestingSupport;
 import net.shibboleth.idp.profile.testing.RequestContextBuilder;
-import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
-import net.shibboleth.utilities.java.support.security.DataSealerException;
 import net.shibboleth.utilities.java.support.security.impl.SecureRandomIdentifierGenerationStrategy;
 
 // Checkstyle: ThrowsCount OFF
@@ -60,13 +54,13 @@ public class RevokeTokenTest extends BaseTokenClaimsSetTest {
 
     private RefreshTokenClaimsSet rfClaimsSet;
 
-    TokenRevocationRequest revokeAccessToken;
-
-    TokenRevocationRequest revokeRefreshToken;
-
-    TokenRevocationRequest unknownToken;
-
     private RevocationCache revocationCache;
+    
+    private RequestContext src;
+    
+    private ProfileRequestContext prc;
+    
+    private OAuth2TokenMgmtResponseContext tokenCtx;
 
     @BeforeMethod
     protected void setUp() throws Exception {
@@ -79,19 +73,7 @@ public class RevokeTokenTest extends BaseTokenClaimsSetTest {
         revocationCache.setEntryExpiration(Duration.ofHours(1));
         revocationCache.setStorage(storageService);
         revocationCache.initialize();
-    }
 
-    @AfterMethod
-    protected void tearDown() {
-        revocationCache.destroy();
-        revocationCache = null;
-
-        storageService.destroy();
-        storageService = null;
-    }
-
-    protected void init()
-            throws ComponentInitializationException, NoSuchAlgorithmException, DataSealerException, URISyntaxException {
         // init tokens
         final AuthorizeCodeClaimsSet acClaimsSet = new AuthorizeCodeClaimsSet.Builder()
                 .setJWTID(new SecureRandomIdentifierGenerationStrategy())
@@ -107,50 +89,43 @@ public class RevokeTokenTest extends BaseTokenClaimsSetTest {
                 build();
         atClaimsSet = new AccessTokenClaimsSet.Builder(acClaimsSet, scope, dlClaims, dlClaimsUI, iat, exp).build();
         rfClaimsSet = new RefreshTokenClaimsSet.Builder(acClaimsSet, iat, exp).build();
+        
         // init action
         action = new RevokeToken();
-        action.setDataSealer(sealer);
         action.setRevocationCache(revocationCache);
         action.initialize();
-        revokeAccessToken = new TokenRevocationRequest(new URI("https://example.com"), new ClientID(),
-                new BearerAccessToken(atClaimsSet.serialize(sealer)));
-        // Using BearerAccessToken for refresh token has no relevance to test
-        revokeRefreshToken = new TokenRevocationRequest(new URI("https://example.com"), new ClientID(),
-                new BearerAccessToken(rfClaimsSet.serialize(sealer)));
-        unknownToken = new TokenRevocationRequest(new URI("https://example.com"), new ClientID(),
-                new BearerAccessToken("sometoken"));
+
+        src = new RequestContextBuilder().buildRequestContext();
+        prc = new WebflowRequestContextProfileRequestContextLookup().apply(src);
+        tokenCtx = prc.getOutboundMessageContext().getSubcontext(OAuth2TokenMgmtResponseContext.class, true);
+    }
+
+    @AfterMethod
+    protected void tearDown() {
+        action.destroy();
+        revocationCache.destroy();
+        storageService.destroy();
     }
 
     @Test
-    public void testRevokeAccessToken()
-            throws ComponentInitializationException, NoSuchAlgorithmException, DataSealerException, URISyntaxException {
-        init();
-        final RequestContext requestContext =
-                new RequestContextBuilder().setInboundMessage(revokeAccessToken).buildRequestContext();
+    public void testNoToken() {
+        ActionTestingSupport.assertProceedEvent(action.execute(src));
+    }
+    
+    @Test
+    public void testRevokeAccessToken() {
         Assert.assertFalse(revocationCache.isRevoked(RevocationCacheContexts.AUTHORIZATION_CODE, atClaimsSet.getID()));
-        ActionTestingSupport.assertProceedEvent(action.execute(requestContext));
+        tokenCtx.setTokenClaimsSet(atClaimsSet.getClaimsSet());
+        ActionTestingSupport.assertProceedEvent(action.execute(src));
         Assert.assertTrue(revocationCache.isRevoked(RevocationCacheContexts.AUTHORIZATION_CODE, atClaimsSet.getID()));
     }
 
     @Test
-    public void testRevokeRefreshToken()
-            throws ComponentInitializationException, NoSuchAlgorithmException, DataSealerException, URISyntaxException {
-        init();
-        final RequestContext requestContext =
-                new RequestContextBuilder().setInboundMessage(revokeRefreshToken).buildRequestContext();
+    public void testRevokeRefreshToken() {
         Assert.assertFalse(revocationCache.isRevoked(RevocationCacheContexts.AUTHORIZATION_CODE, rfClaimsSet.getID()));
-        ActionTestingSupport.assertProceedEvent(action.execute(requestContext));
+        tokenCtx.setTokenClaimsSet(rfClaimsSet.getClaimsSet());
+        ActionTestingSupport.assertProceedEvent(action.execute(src));
         Assert.assertTrue(revocationCache.isRevoked(RevocationCacheContexts.AUTHORIZATION_CODE, rfClaimsSet.getID()));
     }
 
-    @Test
-    public void testRevokeSomeUnknownToken()
-            throws ComponentInitializationException, NoSuchAlgorithmException, DataSealerException, URISyntaxException {
-        init();
-        final RequestContext requestContext =
-                new RequestContextBuilder().setInboundMessage(unknownToken).buildRequestContext();
-        ActionTestingSupport.assertProceedEvent(action.execute(requestContext));
-
-    }
-
 }
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/RevocationFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/RevocationFlowTest.java
index eb84b277..c6f5d53e 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/RevocationFlowTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/RevocationFlowTest.java
@@ -28,7 +28,6 @@ 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.BeforeMethod;
 import org.testng.annotations.Test;
 
@@ -37,7 +36,6 @@ import com.nimbusds.jwt.SignedJWT;
 import com.nimbusds.oauth2.sdk.OAuth2Error;
 import com.nimbusds.oauth2.sdk.ParseException;
 import com.nimbusds.oauth2.sdk.Scope;
-import com.nimbusds.oauth2.sdk.TokenIntrospectionErrorResponse;
 import com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod;
 
 import net.shibboleth.idp.plugin.oidc.op.oauth2.messaging.impl.OAuth2RevocationSuccessResponse;

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


More information about the commits mailing list