[java-idp-oidc] branch dev/JOIDC-222 updated: JOIDC-222 - Support for OpenID Federation

Henri Mikkonen henri.mikkonen at iki.fi
Tue Aug 12 12:06:43 UTC 2025


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

hjmikkon pushed a commit to branch dev/JOIDC-222
in repository java-idp-oidc.

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

The following commit(s) were added to refs/heads/dev/JOIDC-222 by this push:
     new 369af52d JOIDC-222 - Support for OpenID Federation
369af52d is described below

commit 369af52d51988b1b9ab9a4afeb7e1a46380b61cc
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Tue Aug 12 15:06:27 2025 +0300

    JOIDC-222 - Support for OpenID Federation
    
    https://shibboleth.atlassian.net/browse/JOIDC-222
    
    Harmonised entity configuration and resolve entity API for caching the response caching
    - Caching time for success responses set in the same way via OIDFederationResponseCachingProfileConfiguration
    - Similarly to resolve entity API, also entity configuration caching now exploits metadata cache instead of (in-memory) storage service
      - Success responses are stored in the cache
---
 ...FederationRegistrationProfileConfiguration.java |  13 ++
 ...erationResponseCachingProfileConfiguration.java |  89 +++++++++++++
 ...CachedErrorResponseLifetimeLookupFunction.java} |   2 +-
 ...chedSuccessResponseLifetimeLookupFunction.java} |   8 +-
 ...ionEntityConfigurationProfileConfiguration.java |  46 +------
 ...ederationResolveEntityProfileConfiguration.java |  47 +------
 ...ionEntityConfigurationProfileConfiguration.java |  16 +--
 ...ederationResolveEntityProfileConfiguration.java |  16 +--
 ...rationResponseCachingProfileConfiguration.java} |  23 +---
 ...ionResponseContainerExpirationTimeStrategy.java |  59 ++++++++
 ...tityConfigurationResponseFetchingStrategy.java} |  24 ++--
 ...faultResolveEntityResponseFetchingStrategy.java |   6 +-
 ...Container.java => NimbusResponseContainer.java} |  25 +---
 ...Criterion.java => NimbusResponseCriterion.java} |  10 +-
 .../metadata/ResolveEntityResponseContainer.java   |  29 +---
 ...a => ResponseContainerExpirationCriterion.java} |   8 +-
 .../BuildResolveEntityErrorResponseFromEvent.java  |  36 ++---
 ...ormOutboundFederationConfigurationResponse.java | 148 ++++++++++++++++-----
 .../impl/FormOutboundResolveEntityResponse.java    |  71 +++++-----
 ...> LookupCachedEntityConfigurationResponse.java} |  71 +++++-----
 ...java => LookupCachedResolveEntityResponse.java} |  45 ++++---
 .../oidfed/profile/impl/OidFederationEventIds.java |   6 +
 ....java => RelyingPartyCachedMessageContext.java} |  18 ++-
 .../profile/impl/ValidateResolveEntityRequest.java |  28 ++--
 .../profile/impl/ValidateSelectedTrustChain.java   |  23 ++--
 ...achedEntityStatementLifetimeLookupFunction.java |  52 --------
 .../entity-configuration-beans.xml                 |  38 ++++--
 .../entity-configuration-flow.xml                  |  27 +---
 .../oidfed/resolve-entity/resolve-entity-beans.xml |   4 +-
 .../oidfed/resolve-entity/resolve-entity-flow.xml  |   2 +-
 .../shibboleth/idp/module/conf/relying-party.xml   |   2 +-
 31 files changed, 517 insertions(+), 475 deletions(-)

diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/config/AbstractOIDFederationRegistrationProfileConfiguration.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/config/AbstractOIDFederationRegistrationProfileConfiguration.java
index 219e6d61..fb3931e5 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/config/AbstractOIDFederationRegistrationProfileConfiguration.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/config/AbstractOIDFederationRegistrationProfileConfiguration.java
@@ -1,3 +1,16 @@
+/*
+ * Licensed 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.oidfed.config;
 
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/config/AbstractOIDFederationResponseCachingProfileConfiguration.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/config/AbstractOIDFederationResponseCachingProfileConfiguration.java
new file mode 100644
index 00000000..654f958a
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/config/AbstractOIDFederationResponseCachingProfileConfiguration.java
@@ -0,0 +1,89 @@
+/*
+ * Licensed 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.oidfed.config;
+
+import java.time.Duration;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.profile.config.AbstractConditionalProfileConfiguration;
+import net.shibboleth.shared.annotation.ParameterName;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.annotation.constraint.Positive;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.logic.FunctionSupport;
+
+/**
+ * Abstract implementation class for profile configurations related OpenID Federation response caching configuration.
+ */
+public abstract class AbstractOIDFederationResponseCachingProfileConfiguration
+    extends AbstractConditionalProfileConfiguration
+    implements OIDFederationResponseCachingProfileConfiguration {
+
+    /** Lookup function to supply cached success response lifetime. */
+    @Nonnull private Function<ProfileRequestContext,Duration> cachedSuccessResponseLifetimeLookupStrategy;
+
+    /**
+     * Constructor.
+     * 
+     * @param id ID of the communication profile, never null or empty
+     */
+    public AbstractOIDFederationResponseCachingProfileConfiguration(
+            @Nonnull @NotEmpty @ParameterName(name="id") final String id) {
+        super(id);
+        cachedSuccessResponseLifetimeLookupStrategy = FunctionSupport.constant(Duration.ofMinutes(5));
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    @Positive @Nonnull
+    public Duration getCachedSuccessResponseLifetime(@Nullable final ProfileRequestContext profileRequestContext) {
+        final Duration lifetime = cachedSuccessResponseLifetimeLookupStrategy.apply(profileRequestContext);
+
+        Constraint.isTrue(lifetime != null && !lifetime.isZero() && !lifetime.isNegative(),
+                "Success response lifetime must be greater than 0");
+        assert lifetime != null;
+        return lifetime;
+    }
+
+    /**
+     * Set the lifetime of a cached success response.
+     * 
+     * @param lifetime lifetime of a cached success response
+     */
+    public void setCachedSuccessResponseLifetime(@Positive @Nonnull final Duration lifetime) {
+        final Duration successLifetime = Constraint.isNotNull(lifetime,
+                "Cached success response lifetime cannot be null");
+        Constraint.isTrue(!successLifetime.isZero() && !successLifetime.isNegative(),
+                "Cached success response lifetime must be greater than 0");
+
+        cachedSuccessResponseLifetimeLookupStrategy = FunctionSupport.constant(successLifetime);
+    }
+
+    /**
+     * Set a lookup strategy for the cached success response lifetime.
+     *
+     * @param strategy lookup strategy
+     */
+    public void setCachedSuccessResponseLifetimeLookupStrategy(
+            @Nullable final Function<ProfileRequestContext,Duration> strategy) {
+        cachedSuccessResponseLifetimeLookupStrategy = Constraint.isNotNull(strategy, "Lookup strategy cannot be null");
+    }
+
+}
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/config/ResolveEntityCachedErrorResponseLifetimeLookupFunction.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/config/CachedErrorResponseLifetimeLookupFunction.java
similarity index 96%
rename from idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/config/ResolveEntityCachedErrorResponseLifetimeLookupFunction.java
rename to idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/config/CachedErrorResponseLifetimeLookupFunction.java
index d22579c7..01ded1d0 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/config/ResolveEntityCachedErrorResponseLifetimeLookupFunction.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/config/CachedErrorResponseLifetimeLookupFunction.java
@@ -29,7 +29,7 @@ import net.shibboleth.profile.context.navigate.AbstractRelyingPartyLookupFunctio
  * 
  * <p>If a specific setting is unavailable, a null value is returned.</p>
  */
-public class ResolveEntityCachedErrorResponseLifetimeLookupFunction
+public class CachedErrorResponseLifetimeLookupFunction
     extends AbstractRelyingPartyLookupFunction<Duration> {
 
     /** {@inheritDoc} */
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/config/ResolveEntityCachedSuccessResponseLifetimeLookupFunction.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/config/CachedSuccessResponseLifetimeLookupFunction.java
similarity index 80%
rename from idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/config/ResolveEntityCachedSuccessResponseLifetimeLookupFunction.java
rename to idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/config/CachedSuccessResponseLifetimeLookupFunction.java
index 1ffecda0..b083076c 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/config/ResolveEntityCachedSuccessResponseLifetimeLookupFunction.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/config/CachedSuccessResponseLifetimeLookupFunction.java
@@ -25,11 +25,11 @@ import net.shibboleth.profile.context.navigate.AbstractRelyingPartyLookupFunctio
 
 /**
  * A function that returns
- * {@link OIDFederationResolveEntityProfileConfiguration#getCachedSuccessResponseLifetime(ProfileRequestContext)}.
+ * {@link OIDFederationResponseCachingProfileConfiguration#getCachedSuccessResponseLifetime(ProfileRequestContext)}.
  * 
  * <p>If a specific setting is unavailable, a null value is returned.</p>
  */
-public class ResolveEntityCachedSuccessResponseLifetimeLookupFunction
+public class CachedSuccessResponseLifetimeLookupFunction
     extends AbstractRelyingPartyLookupFunction<Duration> {
 
     /** {@inheritDoc} */
@@ -37,8 +37,8 @@ public class ResolveEntityCachedSuccessResponseLifetimeLookupFunction
     @Nullable public Duration apply(@Nullable final ProfileRequestContext input) {
         return Optional.ofNullable(getRelyingPartyContextLookupStrategy().apply(input))
                 .map(relyingPartyContext -> relyingPartyContext.getProfileConfig())
-                .filter(OIDFederationResolveEntityProfileConfiguration.class::isInstance)
-                .map(OIDFederationResolveEntityProfileConfiguration.class::cast)
+                .filter(OIDFederationResponseCachingProfileConfiguration.class::isInstance)
+                .map(OIDFederationResponseCachingProfileConfiguration.class::cast)
                 .map(config -> config.getCachedSuccessResponseLifetime(input))
                 .orElse(null);
     }
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/config/DefaultOIDFederationEntityConfigurationProfileConfiguration.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/config/DefaultOIDFederationEntityConfigurationProfileConfiguration.java
index bd020687..c885f7a6 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/config/DefaultOIDFederationEntityConfigurationProfileConfiguration.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/config/DefaultOIDFederationEntityConfigurationProfileConfiguration.java
@@ -25,7 +25,6 @@ import javax.annotation.Nullable;
 
 import org.opensaml.profile.context.ProfileRequestContext;
 
-import net.shibboleth.profile.config.AbstractConditionalProfileConfiguration;
 import net.shibboleth.shared.annotation.constraint.NonnullElements;
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
 import net.shibboleth.shared.annotation.constraint.NotLive;
@@ -38,8 +37,9 @@ import net.shibboleth.shared.logic.FunctionSupport;
 /**
  * Implementation of a profile configuration for the OpenID Federation Entity Configuration.
  */
-public class DefaultOIDFederationEntityConfigurationProfileConfiguration extends AbstractConditionalProfileConfiguration
-        implements OIDFederationEntityConfigurationProfileConfiguration {
+public class DefaultOIDFederationEntityConfigurationProfileConfiguration
+    extends AbstractOIDFederationResponseCachingProfileConfiguration
+    implements OIDFederationEntityConfigurationProfileConfiguration {
 
     /** OIDC provider information profile counter name. */
     @Nonnull @NotEmpty public static final String PROFILE_COUNTER = "net.shibboleth.idp.profiles.oidfed.configuration";
@@ -53,9 +53,6 @@ public class DefaultOIDFederationEntityConfigurationProfileConfiguration extends
     /** Lookup function to supply entity statement lifetime. */
     @Nonnull private Function<ProfileRequestContext,Duration> entityStatementLifetimeLookupStrategy;
 
-    /** Lookup function to supply cached entity statement lifetime. */
-    @Nonnull private Function<ProfileRequestContext,Duration> cachedEntityStatementLifetimeLookupStrategy;
-
     /** Lookup function to supply strategy bi-function for manipulating entity statement claims set. */ 
     @Nonnull
     private Function<ProfileRequestContext,BiFunction<ProfileRequestContext,Map<String,Object>,Map<String,Object>>>
@@ -78,7 +75,6 @@ public class DefaultOIDFederationEntityConfigurationProfileConfiguration extends
         issuerLookupStrategy = FunctionSupport.constant(null);
         authorityHintsLookupStrategy = FunctionSupport.constant(CollectionSupport.emptyList());
         entityStatementLifetimeLookupStrategy = FunctionSupport.constant(Duration.ofHours(24));
-        cachedEntityStatementLifetimeLookupStrategy = FunctionSupport.constant(Duration.ofSeconds(10));
         entityStatementClaimsSetManipulationStrategyLookupStrategy = FunctionSupport.constant(null);
     }
     
@@ -169,42 +165,6 @@ public class DefaultOIDFederationEntityConfigurationProfileConfiguration extends
         entityStatementLifetimeLookupStrategy = Constraint.isNotNull(strategy, "Lookup strategy cannot be null");
     }
 
-    /** {@inheritDoc} */
-    @Override
-    @Positive @Nonnull
-    public Duration getCachedEntityStatementLifetime(@Nullable final ProfileRequestContext profileRequestContext) {
-        final Duration lifetime = cachedEntityStatementLifetimeLookupStrategy.apply(profileRequestContext);
-
-        Constraint.isTrue(lifetime != null && !lifetime.isZero() && !lifetime.isNegative(),
-                "Entity statement lifetime must be greater than 0");
-        assert lifetime != null;
-        return lifetime;
-    }
-
-    /**
-     * Set the lifetime of a cached entity statement.
-     * 
-     * @param lifetime lifetime of a cached entity statement
-     */
-    public void setCachedEntityStatementLifetime(@Positive @Nonnull final Duration lifetime) {
-        final Duration statementLifetime = Constraint.isNotNull(lifetime,
-                "Cached entity statement lifetime cannot be null");
-        Constraint.isTrue(!statementLifetime.isZero() && !statementLifetime.isNegative(),
-                "Cached entity statement lifetime must be greater than 0");
-
-        cachedEntityStatementLifetimeLookupStrategy = FunctionSupport.constant(statementLifetime);
-    }
-
-    /**
-     * Set a lookup strategy for the cached entity statement lifetime.
-     *
-     * @param strategy lookup strategy
-     */
-    public void setCachedEntityStatementLifetimeLookupStrategy(
-            @Nullable final Function<ProfileRequestContext,Duration> strategy) {
-        cachedEntityStatementLifetimeLookupStrategy = Constraint.isNotNull(strategy, "Lookup strategy cannot be null");
-    }
-
     /** {@inheritDoc} */
     @Override
     @Nullable
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/config/DefaultOIDFederationResolveEntityProfileConfiguration.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/config/DefaultOIDFederationResolveEntityProfileConfiguration.java
index a0dfb3d3..ca140f84 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/config/DefaultOIDFederationResolveEntityProfileConfiguration.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/config/DefaultOIDFederationResolveEntityProfileConfiguration.java
@@ -22,7 +22,6 @@ import javax.annotation.Nullable;
 
 import org.opensaml.profile.context.ProfileRequestContext;
 
-import net.shibboleth.profile.config.AbstractConditionalProfileConfiguration;
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
 import net.shibboleth.shared.annotation.constraint.Positive;
 import net.shibboleth.shared.logic.Constraint;
@@ -31,8 +30,9 @@ import net.shibboleth.shared.logic.FunctionSupport;
 /**
  * Implementation of a profile configuration for the OpenID Federation Resolve Entity.
  */
-public class DefaultOIDFederationResolveEntityProfileConfiguration extends AbstractConditionalProfileConfiguration
-        implements OIDFederationResolveEntityProfileConfiguration {
+public class DefaultOIDFederationResolveEntityProfileConfiguration
+    extends AbstractOIDFederationResponseCachingProfileConfiguration
+    implements OIDFederationResolveEntityProfileConfiguration {
 
     /** OIDC provider information profile counter name. */
     @Nonnull @NotEmpty public static final String PROFILE_COUNTER = "net.shibboleth.idp.profiles.oidfed.resolve-entity";
@@ -40,9 +40,6 @@ public class DefaultOIDFederationResolveEntityProfileConfiguration extends Abstr
     /** Lookup function to override issuer value. */
     @Nonnull private Function<ProfileRequestContext,String> issuerLookupStrategy;
 
-    /** Lookup function to supply cached success response lifetime. */
-    @Nonnull private Function<ProfileRequestContext,Duration> cachedSuccessResponseLifetimeLookupStrategy;
-
     /** Lookup function to supply cached error response lifetime. */
     @Nonnull private Function<ProfileRequestContext,Duration> cachedErrorResponseLifetimeLookupStrategy;
 
@@ -61,7 +58,6 @@ public class DefaultOIDFederationResolveEntityProfileConfiguration extends Abstr
     public DefaultOIDFederationResolveEntityProfileConfiguration(@Nonnull @NotEmpty final String profileId) {
         super(profileId);
         issuerLookupStrategy = FunctionSupport.constant(null);
-        cachedSuccessResponseLifetimeLookupStrategy = FunctionSupport.constant(Duration.ofMinutes(5));
         cachedErrorResponseLifetimeLookupStrategy = FunctionSupport.constant(Duration.ofMinutes(5));
     }
     
@@ -89,42 +85,6 @@ public class DefaultOIDFederationResolveEntityProfileConfiguration extends Abstr
         issuerLookupStrategy = Constraint.isNotNull(strategy, "Issuer lookup strategy cannot be null");
     }
 
-    /** {@inheritDoc} */
-    @Override
-    @Positive @Nonnull
-    public Duration getCachedSuccessResponseLifetime(@Nullable final ProfileRequestContext profileRequestContext) {
-        final Duration lifetime = cachedSuccessResponseLifetimeLookupStrategy.apply(profileRequestContext);
-
-        Constraint.isTrue(lifetime != null && !lifetime.isZero() && !lifetime.isNegative(),
-                "Success response lifetime must be greater than 0");
-        assert lifetime != null;
-        return lifetime;
-    }
-
-    /**
-     * Set the lifetime of a cached success response.
-     * 
-     * @param lifetime lifetime of a cached success response
-     */
-    public void setCachedSuccessResponseLifetime(@Positive @Nonnull final Duration lifetime) {
-        final Duration successLifetime = Constraint.isNotNull(lifetime,
-                "Cached success response lifetime cannot be null");
-        Constraint.isTrue(!successLifetime.isZero() && !successLifetime.isNegative(),
-                "Cached success response lifetime must be greater than 0");
-
-        cachedSuccessResponseLifetimeLookupStrategy = FunctionSupport.constant(successLifetime);
-    }
-
-    /**
-     * Set a lookup strategy for the cached success response lifetime.
-     *
-     * @param strategy lookup strategy
-     */
-    public void setCachedSuccessResponseLifetimeLookupStrategy(
-            @Nullable final Function<ProfileRequestContext,Duration> strategy) {
-        cachedSuccessResponseLifetimeLookupStrategy = Constraint.isNotNull(strategy, "Lookup strategy cannot be null");
-    }
-
     /** {@inheritDoc} */
     @Override
     @Positive @Nonnull
@@ -160,5 +120,4 @@ public class DefaultOIDFederationResolveEntityProfileConfiguration extends Abstr
             @Nullable final Function<ProfileRequestContext,Duration> strategy) {
         cachedErrorResponseLifetimeLookupStrategy = Constraint.isNotNull(strategy, "Lookup strategy cannot be null");
     }
-
 }
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/config/OIDFederationEntityConfigurationProfileConfiguration.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/config/OIDFederationEntityConfigurationProfileConfiguration.java
index 1460958e..fa05db09 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/config/OIDFederationEntityConfigurationProfileConfiguration.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/config/OIDFederationEntityConfigurationProfileConfiguration.java
@@ -33,7 +33,8 @@ import net.shibboleth.shared.annotation.constraint.Unmodifiable;
  * Profile configuration for an OpenID Federation Entity Configuration.
  */
 public interface OIDFederationEntityConfigurationProfileConfiguration extends OverriddenIssuerProfileConfiguration, 
-    OIDFederationProfileConfiguration, OIDFederationEntityStatementProducingProfileConfiguration {
+    OIDFederationProfileConfiguration, OIDFederationEntityStatementProducingProfileConfiguration,
+    OIDFederationResponseCachingProfileConfiguration{
     
     /** OIDC base protocol URI. Section 4 is relevant. */
     public static final String PROTOCOL_URI = "https://openid.net/specs/openid-federation-1_0.html";
@@ -64,17 +65,4 @@ public interface OIDFederationEntityConfigurationProfileConfiguration extends Ov
     @ConfigurationSetting(name="entityStatementLifetime")
     @Positive @Nonnull Duration getEntityStatementLifetime(@Nullable final ProfileRequestContext profileRequestContext);
 
-    /**
-     * Get cached entity statement lifetime.
-     * 
-     * <p>Defaults to 10 seconds.</p>
-     * 
-     * @param profileRequestContext profile request context
-     * 
-     * @return cached entity statement lifetime
-     */
-    @ConfigurationSetting(name="cachedEntityStatementLifetime")
-    @Positive @Nonnull
-    Duration getCachedEntityStatementLifetime(@Nullable final ProfileRequestContext profileRequestContext);
-
 }
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/config/OIDFederationResolveEntityProfileConfiguration.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/config/OIDFederationResolveEntityProfileConfiguration.java
index f5afddaa..6aaac8c8 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/config/OIDFederationResolveEntityProfileConfiguration.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/config/OIDFederationResolveEntityProfileConfiguration.java
@@ -29,7 +29,7 @@ import net.shibboleth.shared.annotation.constraint.Positive;
  * Profile configuration for an OpenID Federation Resolve Entity.
  */
 public interface OIDFederationResolveEntityProfileConfiguration extends OverriddenIssuerProfileConfiguration, 
-    OIDFederationProfileConfiguration {
+    OIDFederationProfileConfiguration, OIDFederationResponseCachingProfileConfiguration {
     
     /** OIDC base protocol URI. */
     public static final String PROTOCOL_URI = "https://openid.net/specs/openid-federation-1_0.html";
@@ -37,19 +37,6 @@ public interface OIDFederationResolveEntityProfileConfiguration extends Overridd
     /** ID for this profile configuration. */
     public static final String PROFILE_ID = "http://shibboleth.net/ns/profiles/oidfed/resolve-entity";
 
-    /**
-     * Get cached success response lifetime.
-     * 
-     * <p>Defaults to 5 minutes.</p>
-     * 
-     * @param profileRequestContext profile request context
-     * 
-     * @return cached success response lifetime
-     */
-    @ConfigurationSetting(name="cachedSuccessResponseLifetime")
-    @Positive @Nonnull
-    Duration getCachedSuccessResponseLifetime(@Nullable final ProfileRequestContext profileRequestContext);
-
     /**
      * Get cached error response lifetime.
      * 
@@ -62,5 +49,4 @@ public interface OIDFederationResolveEntityProfileConfiguration extends Overridd
     @ConfigurationSetting(name="cachedErrorResponseLifetime")
     @Positive @Nonnull
     Duration getCachedErrorResponseLifetime(@Nullable final ProfileRequestContext profileRequestContext);
-
 }
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/config/OIDFederationResolveEntityProfileConfiguration.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/config/OIDFederationResponseCachingProfileConfiguration.java
similarity index 62%
copy from idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/config/OIDFederationResolveEntityProfileConfiguration.java
copy to idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/config/OIDFederationResponseCachingProfileConfiguration.java
index f5afddaa..5a0f143e 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/config/OIDFederationResolveEntityProfileConfiguration.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/config/OIDFederationResponseCachingProfileConfiguration.java
@@ -26,17 +26,11 @@ import net.shibboleth.shared.annotation.ConfigurationSetting;
 import net.shibboleth.shared.annotation.constraint.Positive;
 
 /** 
- * Profile configuration for an OpenID Federation Resolve Entity.
+ * Common interface for OpenID Federation Profiles producing cacheable response messages.
  */
-public interface OIDFederationResolveEntityProfileConfiguration extends OverriddenIssuerProfileConfiguration, 
+public interface OIDFederationResponseCachingProfileConfiguration extends OverriddenIssuerProfileConfiguration, 
     OIDFederationProfileConfiguration {
     
-    /** OIDC base protocol URI. */
-    public static final String PROTOCOL_URI = "https://openid.net/specs/openid-federation-1_0.html";
-
-    /** ID for this profile configuration. */
-    public static final String PROFILE_ID = "http://shibboleth.net/ns/profiles/oidfed/resolve-entity";
-
     /**
      * Get cached success response lifetime.
      * 
@@ -50,17 +44,4 @@ public interface OIDFederationResolveEntityProfileConfiguration extends Overridd
     @Positive @Nonnull
     Duration getCachedSuccessResponseLifetime(@Nullable final ProfileRequestContext profileRequestContext);
 
-    /**
-     * Get cached error response lifetime.
-     * 
-     * <p>Defaults to 5 minutes.</p>
-     * 
-     * @param profileRequestContext profile request context
-     * 
-     * @return cached error response lifetime
-     */
-    @ConfigurationSetting(name="cachedErrorResponseLifetime")
-    @Positive @Nonnull
-    Duration getCachedErrorResponseLifetime(@Nullable final ProfileRequestContext profileRequestContext);
-
 }
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultEntityConfigurationResponseContainerExpirationTimeStrategy.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultEntityConfigurationResponseContainerExpirationTimeStrategy.java
new file mode 100644
index 00000000..b0058b50
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultEntityConfigurationResponseContainerExpirationTimeStrategy.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed 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.oidfed.metadata;
+
+import java.time.Instant;
+import java.util.Optional;
+import java.util.function.Function;
+
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.ThreadSafe;
+
+import com.nimbusds.openid.connect.sdk.federation.config.FederationEntityConfigurationSuccessResponse;
+
+import net.shibboleth.oidc.metadata.cache.ExpirationTimeContext;
+
+/**
+ * Default strategy for fetching expiration time for the entity configuration response container. The expiration instant
+ * is fetched from which is before: the success response message's JWT expiration time or the instant returned by
+ * {@link NimbusResponseContainer#getExpirationInstant()}.
+ */
+ at ThreadSafe
+public class DefaultEntityConfigurationResponseContainerExpirationTimeStrategy
+        implements Function<ExpirationTimeContext<NimbusResponseContainer>, Instant> {
+
+    /** {@inheritDoc} */
+    @Nullable public Instant apply(@Nullable final ExpirationTimeContext<NimbusResponseContainer> context) {
+        if (context == null) {
+            return null;
+        }
+        final Instant contextExpiration = context.getNow().plus(context.getMaxCacheDuration());
+        final NimbusResponseContainer container = context.getMetadata();
+        if (container == null || container.getExpirationInstant() == null) {
+            return contextExpiration;
+        }
+        final Instant containerExpiration = container.getExpirationInstant();
+        if (container.getResponse() instanceof FederationEntityConfigurationSuccessResponse successResponse) {
+            final Instant jwtExpiration = Optional.ofNullable(
+                    successResponse.getEntityStatement().getClaimsSet().getExpirationTime())
+                    .map(date -> date.toInstant())
+                    .orElse(null);
+            return jwtExpiration == null ? containerExpiration :
+                    jwtExpiration.isBefore(containerExpiration) ? jwtExpiration : containerExpiration;
+        }
+        return containerExpiration;
+    }
+
+}
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultResolveEntityResponseFetchingStrategy.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultEntityConfigurationResponseFetchingStrategy.java
similarity index 58%
copy from idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultResolveEntityResponseFetchingStrategy.java
copy to idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultEntityConfigurationResponseFetchingStrategy.java
index 13270d4f..1862386b 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultResolveEntityResponseFetchingStrategy.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultEntityConfigurationResponseFetchingStrategy.java
@@ -27,39 +27,33 @@ import net.shibboleth.shared.primitive.LoggerFactory;
 import net.shibboleth.shared.resolver.CriteriaSet;
 
 /**
- * Default strategy for fetching resolve entity response container via criteria.
+ * Default strategy for fetching entity configuration response container via criteria.
  */
 @ThreadSafeAfterInit
-public class DefaultResolveEntityResponseFetchingStrategy extends AbstractIdentifiableInitializableComponent
-        implements Function<CriteriaSet, ResolveEntityResponseContainer> {
+public class DefaultEntityConfigurationResponseFetchingStrategy extends AbstractIdentifiableInitializableComponent
+        implements Function<CriteriaSet, NimbusResponseContainer> {
 
     /** Class logger. */
-    @Nonnull private Logger log = LoggerFactory.getLogger(DefaultResolveEntityResponseFetchingStrategy.class);
+    @Nonnull private Logger log = LoggerFactory.getLogger(DefaultEntityConfigurationResponseFetchingStrategy.class);
 
     /** {@inheritDoc} */
     @Override @Nullable
-    public ResolveEntityResponseContainer apply(@Nullable final CriteriaSet criteria) {
+    public NimbusResponseContainer apply(@Nullable final CriteriaSet criteria) {
         checkComponentActive();
         if (criteria == null) {
             return null;
         }
-        final ResolveEntityRequestCriterion requestCriterion = criteria.get(ResolveEntityRequestCriterion.class);
-        if (requestCriterion == null) {
-            log.debug("No request criterion given, returning null");
-            return null;
-        }
-        final ResolveEntityResponseCriterion responseCriterion = criteria.get(ResolveEntityResponseCriterion.class);
+        final NimbusResponseCriterion responseCriterion = criteria.get(NimbusResponseCriterion.class);
         if (responseCriterion == null) {
             log.debug("No response criterion given, returning null");
             return null;
         }
-        final ResolveEntityContainerExpirationCriterion expirationCriterion =
-                criteria.get(ResolveEntityContainerExpirationCriterion.class);
+        final ResponseContainerExpirationCriterion expirationCriterion =
+                criteria.get(ResponseContainerExpirationCriterion.class);
         if (expirationCriterion == null) {
             log.debug("No expiration criterion given, returning null");
             return null;
         }
-        return new ResolveEntityResponseContainer(responseCriterion.getResponse(), requestCriterion.getRequest(),
-                expirationCriterion.getExpirationInstant());
+        return new NimbusResponseContainer(responseCriterion.getResponse(), expirationCriterion.getExpirationInstant());
     }
 }
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultResolveEntityResponseFetchingStrategy.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultResolveEntityResponseFetchingStrategy.java
index 13270d4f..70ba1cc8 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultResolveEntityResponseFetchingStrategy.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/DefaultResolveEntityResponseFetchingStrategy.java
@@ -48,13 +48,13 @@ public class DefaultResolveEntityResponseFetchingStrategy extends AbstractIdenti
             log.debug("No request criterion given, returning null");
             return null;
         }
-        final ResolveEntityResponseCriterion responseCriterion = criteria.get(ResolveEntityResponseCriterion.class);
+        final NimbusResponseCriterion responseCriterion = criteria.get(NimbusResponseCriterion.class);
         if (responseCriterion == null) {
             log.debug("No response criterion given, returning null");
             return null;
         }
-        final ResolveEntityContainerExpirationCriterion expirationCriterion =
-                criteria.get(ResolveEntityContainerExpirationCriterion.class);
+        final ResponseContainerExpirationCriterion expirationCriterion =
+                criteria.get(ResponseContainerExpirationCriterion.class);
         if (expirationCriterion == null) {
             log.debug("No expiration criterion given, returning null");
             return null;
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/ResolveEntityResponseContainer.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/NimbusResponseContainer.java
similarity index 64%
copy from idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/ResolveEntityResponseContainer.java
copy to idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/NimbusResponseContainer.java
index 1ae9856f..9928613a 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/ResolveEntityResponseContainer.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/NimbusResponseContainer.java
@@ -21,23 +21,19 @@ import javax.annotation.Nonnull;
 
 import com.nimbusds.oauth2.sdk.Response;
 
-import net.shibboleth.idp.plugin.oidc.op.oidfed.messaging.impl.ResolveEntityRequest;
 import net.shibboleth.shared.logic.Constraint;
 
 /**
- * A container class for metadata caches carrying request and response message details related to Resolve Entity API.
+ * A container class for metadata caches carrying Nimbus response message.
  */
-public class ResolveEntityResponseContainer implements Serializable {
+public class NimbusResponseContainer implements Serializable {
 
     /** Serial version UID. */
-    private static final long serialVersionUID = 756269369356865370L;
+    private static final long serialVersionUID = 656269369356865370L;
 
     /** Response message. */
     @Nonnull private final Response response;
 
-    /** Request message. */
-    @Nonnull private final ResolveEntityRequest request;
-
     /** Expiration instant for this container. */
     @Nonnull private final Instant expiration;
 
@@ -45,13 +41,11 @@ public class ResolveEntityResponseContainer implements Serializable {
      * Constructor.
      *
      * @param responseMessage response message
-     * @param requestMessage request message
      * @param expirationInstant expiration instant
      */
-    public ResolveEntityResponseContainer(@Nonnull final Response responseMessage,
-            @Nonnull final ResolveEntityRequest requestMessage, @Nonnull final Instant expirationInstant) {
+    public NimbusResponseContainer(@Nonnull final Response responseMessage,
+            @Nonnull final Instant expirationInstant) {
         response = Constraint.isNotNull(responseMessage, "Response message cannot be null");
-        request = Constraint.isNotNull(requestMessage, "Request message cannot be null");
         expiration = Constraint.isNotNull(expirationInstant, "Expiration instant cannot be null");
     }
 
@@ -64,15 +58,6 @@ public class ResolveEntityResponseContainer implements Serializable {
         return response;
     }
 
-    /**
-     * Get request message.
-     * 
-     * @return request message
-     */
-    public ResolveEntityRequest getRequest() {
-        return request;
-    }
-
     /**
      * Get expiration instant.
      * 
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/ResolveEntityResponseCriterion.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/NimbusResponseCriterion.java
similarity index 81%
rename from idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/ResolveEntityResponseCriterion.java
rename to idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/NimbusResponseCriterion.java
index 43691690..bcc0dcda 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/ResolveEntityResponseCriterion.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/NimbusResponseCriterion.java
@@ -24,9 +24,9 @@ import net.shibboleth.shared.logic.Constraint;
 import net.shibboleth.shared.resolver.Criterion;
 
 /**
- * A {@link Criterion} representing response message to a resolve entity request.
+ * A {@link Criterion} carrying Nimbus response message.
  */
-public class ResolveEntityResponseCriterion implements Criterion {
+public class NimbusResponseCriterion implements Criterion {
 
     /** The response message. */
     @Nonnull private final Response response;
@@ -36,7 +36,7 @@ public class ResolveEntityResponseCriterion implements Criterion {
      *
      * @param responseMessage response message, must not be null
      */
-    public ResolveEntityResponseCriterion(@Nonnull final Response responseMessage) {
+    public NimbusResponseCriterion(@Nonnull final Response responseMessage) {
         response = Constraint.isNotNull(responseMessage, "Response cannot be null");
     }
 
@@ -52,7 +52,7 @@ public class ResolveEntityResponseCriterion implements Criterion {
     /** {@inheritDoc} */
     @Override
     public String toString() {
-        return "ResolveEntityResponseCriterion [response=" + response + "]";
+        return "NimbusResponseCriterion [response=" + response + "]";
     }
 
     /** {@inheritDoc} */
@@ -73,7 +73,7 @@ public class ResolveEntityResponseCriterion implements Criterion {
         if (getClass() != obj.getClass()) {
             return false;
         }
-        final ResolveEntityResponseCriterion other = (ResolveEntityResponseCriterion) obj;
+        final NimbusResponseCriterion other = (NimbusResponseCriterion) obj;
         return response.equals(other.response);
     }
 
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/ResolveEntityResponseContainer.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/ResolveEntityResponseContainer.java
index 1ae9856f..103a436a 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/ResolveEntityResponseContainer.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/ResolveEntityResponseContainer.java
@@ -27,20 +27,14 @@ import net.shibboleth.shared.logic.Constraint;
 /**
  * A container class for metadata caches carrying request and response message details related to Resolve Entity API.
  */
-public class ResolveEntityResponseContainer implements Serializable {
+public class ResolveEntityResponseContainer extends NimbusResponseContainer implements Serializable {
 
     /** Serial version UID. */
     private static final long serialVersionUID = 756269369356865370L;
 
-    /** Response message. */
-    @Nonnull private final Response response;
-
     /** Request message. */
     @Nonnull private final ResolveEntityRequest request;
 
-    /** Expiration instant for this container. */
-    @Nonnull private final Instant expiration;
-
     /**
      * Constructor.
      *
@@ -50,18 +44,8 @@ public class ResolveEntityResponseContainer implements Serializable {
      */
     public ResolveEntityResponseContainer(@Nonnull final Response responseMessage,
             @Nonnull final ResolveEntityRequest requestMessage, @Nonnull final Instant expirationInstant) {
-        response = Constraint.isNotNull(responseMessage, "Response message cannot be null");
+        super(responseMessage, expirationInstant);
         request = Constraint.isNotNull(requestMessage, "Request message cannot be null");
-        expiration = Constraint.isNotNull(expirationInstant, "Expiration instant cannot be null");
-    }
-
-    /**
-     * Get response message.
-     * 
-     * @return response message
-     */
-    public Response getResponse() {
-        return response;
     }
 
     /**
@@ -73,13 +57,4 @@ public class ResolveEntityResponseContainer implements Serializable {
         return request;
     }
 
-    /**
-     * Get expiration instant.
-     * 
-     * @return expiration instant
-     */
-    public Instant getExpirationInstant() {
-        return expiration;
-    }
-
 }
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/ResolveEntityContainerExpirationCriterion.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/ResponseContainerExpirationCriterion.java
similarity index 86%
rename from idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/ResolveEntityContainerExpirationCriterion.java
rename to idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/ResponseContainerExpirationCriterion.java
index 83888cab..a969761f 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/ResolveEntityContainerExpirationCriterion.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/ResponseContainerExpirationCriterion.java
@@ -23,9 +23,9 @@ import net.shibboleth.shared.logic.Constraint;
 import net.shibboleth.shared.resolver.Criterion;
 
 /**
- * A {@link Criterion} representing expiration instant for a resolve entity container.
+ * A {@link Criterion} representing expiration instant for a response message container.
  */
-public class ResolveEntityContainerExpirationCriterion implements Criterion {
+public class ResponseContainerExpirationCriterion implements Criterion {
 
     /** The expiration instant. */
     @Nonnull private final Instant instant;
@@ -35,7 +35,7 @@ public class ResolveEntityContainerExpirationCriterion implements Criterion {
      *
      * @param expirationInstant expiration instant, must not be null
      */
-    public ResolveEntityContainerExpirationCriterion(@Nonnull final Instant expirationInstant) {
+    public ResponseContainerExpirationCriterion(@Nonnull final Instant expirationInstant) {
         instant = Constraint.isNotNull(expirationInstant, "Expiration instant cannot be null");
     }
 
@@ -72,7 +72,7 @@ public class ResolveEntityContainerExpirationCriterion implements Criterion {
         if (getClass() != obj.getClass()) {
             return false;
         }
-        final ResolveEntityContainerExpirationCriterion other = (ResolveEntityContainerExpirationCriterion) obj;
+        final ResponseContainerExpirationCriterion other = (ResponseContainerExpirationCriterion) obj;
         return instant.equals(other.instant);
     }
 
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/BuildResolveEntityErrorResponseFromEvent.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/BuildResolveEntityErrorResponseFromEvent.java
index 3562968f..e0117b32 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/BuildResolveEntityErrorResponseFromEvent.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/BuildResolveEntityErrorResponseFromEvent.java
@@ -29,12 +29,12 @@ import org.slf4j.Logger;
 
 import com.nimbusds.oauth2.sdk.ErrorObject;
 
-import net.shibboleth.idp.plugin.oidc.op.oidfed.config.ResolveEntityCachedErrorResponseLifetimeLookupFunction;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.config.CachedErrorResponseLifetimeLookupFunction;
 import net.shibboleth.idp.plugin.oidc.op.oidfed.messaging.impl.ResolveEntityRequest;
-import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.ResolveEntityContainerExpirationCriterion;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.ResponseContainerExpirationCriterion;
 import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.ResolveEntityRequestCriterion;
 import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.ResolveEntityResponseContainer;
-import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.ResolveEntityResponseCriterion;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.NimbusResponseCriterion;
 import net.shibboleth.idp.plugin.oidc.op.profile.impl.AbstractBuildErrorResponseFromEvent;
 import net.shibboleth.oidc.metadata.cache.MetadataCache;
 import net.shibboleth.oidc.metadata.cache.MetadataCacheException;
@@ -47,7 +47,7 @@ import net.shibboleth.shared.resolver.CriteriaSet;
 
 /**
  * This action reads an event from the configured {@link EventContext} lookup strategy, constructs a JSON error response
- * message and attaches it as the outbound message. If {@link RelyingPartyResolveEntityContext} is found, it's exploited
+ * message and attaches it as the outbound message. If {@link RelyingPartyCachedMessageContext} is found, it's exploited
  * for storing the response message in the configured {@link #responseCache}.
  */
 public class BuildResolveEntityErrorResponseFromEvent extends AbstractBuildErrorResponseFromEvent<JSONErrorResponse> {
@@ -63,15 +63,15 @@ public class BuildResolveEntityErrorResponseFromEvent extends AbstractBuildError
 
     /** Strategy used to locate the resolve entity context. */
     @Nonnull
-    private Function<ProfileRequestContext, RelyingPartyResolveEntityContext> resolveEntityContextLookupStrategy;
+    private Function<ProfileRequestContext, RelyingPartyCachedMessageContext> resolveEntityContextLookupStrategy;
 
     /**
      * Constructor.
      */
     public BuildResolveEntityErrorResponseFromEvent() {
-        cachedResponseLifetimeLookupStrategy = new ResolveEntityCachedErrorResponseLifetimeLookupFunction();
-        final Function<ProfileRequestContext, RelyingPartyResolveEntityContext> recls =
-                new ChildContextLookup<>(RelyingPartyResolveEntityContext.class).compose(
+        cachedResponseLifetimeLookupStrategy = new CachedErrorResponseLifetimeLookupFunction();
+        final Function<ProfileRequestContext, RelyingPartyCachedMessageContext> recls =
+                new ChildContextLookup<>(RelyingPartyCachedMessageContext.class).compose(
                         new InboundMessageContextLookup());
         assert recls != null;
         resolveEntityContextLookupStrategy = recls;
@@ -105,7 +105,7 @@ public class BuildResolveEntityErrorResponseFromEvent extends AbstractBuildError
      * @param strategy What to set.
      */
     public void setResolveEntityContextLookupStrategy(
-            @Nonnull final Function<ProfileRequestContext,RelyingPartyResolveEntityContext> strategy) {
+            @Nonnull final Function<ProfileRequestContext,RelyingPartyCachedMessageContext> strategy) {
         checkSetterPreconditions();
         resolveEntityContextLookupStrategy = Constraint.isNotNull(strategy, "Lookup strategy cannot be null!");
     }
@@ -125,19 +125,19 @@ public class BuildResolveEntityErrorResponseFromEvent extends AbstractBuildError
     protected JSONErrorResponse buildErrorResponse(final ErrorObject error,
             final ProfileRequestContext profileRequestContext) {
         final JSONErrorResponse response = new JSONErrorResponse(error);
-        final RelyingPartyResolveEntityContext resolveEntityContext =
+        final RelyingPartyCachedMessageContext resolveEntityContext =
                 resolveEntityContextLookupStrategy.apply(profileRequestContext);
 
-        if (resolveEntityContext != null && resolveEntityContext.getValidatedRequest() != null) {
-            final ResolveEntityResponseCriterion responseCriterion = new ResolveEntityResponseCriterion(response);
+        if (resolveEntityContext != null &&
+                resolveEntityContext.getValidatedRequest() instanceof ResolveEntityRequest resolveEntityRequest) {
+            final NimbusResponseCriterion responseCriterion = new NimbusResponseCriterion(response);
             final Duration cachedResponseLifetime = cachedResponseLifetimeLookupStrategy.apply(profileRequestContext);
             final Instant expiration = Instant.now().plus(cachedResponseLifetime);
             assert expiration != null;
-            final ResolveEntityContainerExpirationCriterion expirationCriterion =
-                    new ResolveEntityContainerExpirationCriterion(expiration);
-            final ResolveEntityRequest validatedRequest = resolveEntityContext.getValidatedRequest();
-            assert validatedRequest != null;
-            final ResolveEntityRequestCriterion requestCriterion = new ResolveEntityRequestCriterion(validatedRequest);
+            final ResponseContainerExpirationCriterion expirationCriterion =
+                    new ResponseContainerExpirationCriterion(expiration);
+            final ResolveEntityRequestCriterion requestCriterion =
+                    new ResolveEntityRequestCriterion(resolveEntityRequest);
             final CriteriaSet criteria = new CriteriaSet(requestCriterion, responseCriterion, expirationCriterion);
             try {
                 final List<ResolveEntityResponseContainer> result = responseCache.get(criteria);
@@ -148,7 +148,7 @@ public class BuildResolveEntityErrorResponseFromEvent extends AbstractBuildError
                     log.debug("{} Response stored into the cache", getLogPrefix());
                 }
             } catch (final MetadataCacheException e) {
-                log.error("{} Could not store the response record into tht metadata cache", getLogPrefix(), e);
+                log.error("{} Could not store the response record into the metadata cache", getLogPrefix(), e);
             }
         }
 
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/FormOutboundFederationConfigurationResponse.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/FormOutboundFederationConfigurationResponse.java
index 635ece0f..226c971c 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/FormOutboundFederationConfigurationResponse.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/FormOutboundFederationConfigurationResponse.java
@@ -14,9 +14,9 @@
 
 package net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl;
 
-import java.io.IOException;
 import java.time.Duration;
 import java.time.Instant;
+import java.util.List;
 import java.util.function.Function;
 
 import javax.annotation.Nonnull;
@@ -27,20 +27,27 @@ import org.opensaml.profile.action.ActionSupport;
 import org.opensaml.profile.action.EventIds;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.opensaml.profile.context.navigate.OutboundMessageContextLookup;
-import org.opensaml.storage.StorageService;
 import org.slf4j.Logger;
 
 import com.nimbusds.jwt.SignedJWT;
 import com.nimbusds.oauth2.sdk.ParseException;
+import com.nimbusds.oauth2.sdk.Response;
 import com.nimbusds.openid.connect.sdk.federation.config.FederationEntityConfigurationSuccessResponse;
 import com.nimbusds.openid.connect.sdk.federation.entities.EntityStatement;
 
-import net.shibboleth.idp.plugin.oidc.op.oidfed.config.OIDFederationEntityConfigurationProfileConfiguration;
-import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate.CachedEntityStatementLifetimeLookupFunction;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.config.CachedSuccessResponseLifetimeLookupFunction;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.NimbusResponseContainer;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.NimbusResponseCriterion;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.ResponseContainerExpirationCriterion;
 import net.shibboleth.idp.profile.AbstractProfileAction;
+import net.shibboleth.oidc.metadata.cache.MetadataCache;
+import net.shibboleth.oidc.metadata.cache.MetadataCacheException;
 import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
+import net.shibboleth.shared.component.ComponentInitializationException;
 import net.shibboleth.shared.logic.Constraint;
 import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.resolver.CriteriaSet;
 
 /**
  * This action builds a response for the OpenID federation configuration request. The response contains an
@@ -53,22 +60,36 @@ public class FormOutboundFederationConfigurationResponse extends AbstractProfile
     /** Class logger. */
     @Nonnull private final Logger log = LoggerFactory.getLogger(FormOutboundFederationConfigurationResponse.class);
 
+    /** Metadata cache for cached response containers. */
+    @NonnullAfterInit private MetadataCache<NimbusResponseContainer> responseCache;
+
+    /** Strategy used to locate the cached message context. */
+    @Nonnull
+    private Function<ProfileRequestContext, RelyingPartyCachedMessageContext> cachedMessageContextLookupStrategy;
+
     /** Strategy used to locate the subcontext to hold the statement. */
     @Nonnull private Function<ProfileRequestContext,EntityStatementContext> entityStatementContextLookupStrategy;
 
-    @Nonnull private Function<ProfileRequestContext,Duration> storageRecordLifetimeLookupStrategy;
-    @NonnullAfterInit private StorageService storageService;
+    /** Strategy used to locate the lifetime for the cached response record. */
+    @Nonnull private Function<ProfileRequestContext,Duration> cachedResponseLifetimeLookupStrategy;
 
     /** JWT used to build entity statement. */
     @Nullable private SignedJWT jwt;
 
+    /** The resolve entity context to operate on. */
+    @NonnullBeforeExec private RelyingPartyCachedMessageContext cachedMessageContext;
+
+    /**
+     * Constructor.
+     */
     public FormOutboundFederationConfigurationResponse() {
         final Function<ProfileRequestContext,EntityStatementContext> escls =
                 new ChildContextLookup<>(EntityStatementContext.class).compose(
                         new OutboundMessageContextLookup());
         assert escls != null;
         entityStatementContextLookupStrategy = escls;
-        storageRecordLifetimeLookupStrategy = new CachedEntityStatementLifetimeLookupFunction();
+        cachedMessageContextLookupStrategy = new ChildContextLookup<>(RelyingPartyCachedMessageContext.class);
+        cachedResponseLifetimeLookupStrategy = new CachedSuccessResponseLifetimeLookupFunction();
     }
 
     /**
@@ -76,14 +97,52 @@ public class FormOutboundFederationConfigurationResponse extends AbstractProfile
      * 
      * @param strategy What to set.
      */
-    public void setMetadataResolver(@Nonnull final Function<ProfileRequestContext,EntityStatementContext> strategy) {
+    public void setEntityStatementContextLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext,EntityStatementContext> strategy) {
         checkSetterPreconditions();
         entityStatementContextLookupStrategy = Constraint.isNotNull(strategy, "Lookup strategy cannot be null!");
     }
 
-    public void setStorageService(@Nonnull final StorageService service) {
+    /**
+     * Set the strategy used to locate the cached message context
+     * 
+     * @param strategy What to set.
+     */
+    public void setCachedMessageContextLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext,RelyingPartyCachedMessageContext> strategy) {
+        checkSetterPreconditions();
+        cachedMessageContextLookupStrategy = Constraint.isNotNull(strategy, "Lookup strategy cannot be null!");
+    }
+
+    /**
+     * Set the metadata cache for cached response containers.
+     * 
+     * @param cache What to set.
+     */
+    public void setResponseCache(@Nonnull final MetadataCache<NimbusResponseContainer> cache) {
+        checkSetterPreconditions();
+        responseCache = Constraint.isNotNull(cache, "Response metadata cache cannot be null");
+    }
+
+    /**
+     * Set the strategy used to locate the lifetime for the cached response record.
+     * 
+     * @param strategy What to set.
+     */
+    public void setCachedResponseLifetimeLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext, Duration> strategy) {
         checkSetterPreconditions();
-        storageService = Constraint.isNotNull(service, "Storage service cannot be null!");
+        cachedResponseLifetimeLookupStrategy = Constraint.isNotNull(strategy, "Lookup strategy cannot be null");
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+
+        if (responseCache == null) {
+            throw new ComponentInitializationException("Response metadata cache cannot be null");
+        }
     }
 
     /** {@inheritDoc} */
@@ -93,47 +152,74 @@ public class FormOutboundFederationConfigurationResponse extends AbstractProfile
         if (!super.doPreExecute(profileRequestContext)) {
             return false;
         }
+        cachedMessageContext = cachedMessageContextLookupStrategy.apply(profileRequestContext);
+        if (cachedMessageContext == null) {
+            log.error("{} Could not resolve cached message context", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+            return false;
+        }
+
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+        final Response cachedResponse = cachedMessageContext.getCachedResponse();
+        if (cachedResponse != null) {
+            log.debug("{} Cached response found, storing in to the outbound message context", getLogPrefix());
+            profileRequestContext.ensureOutboundMessageContext().setMessage(cachedResponse);
+            return;
+        }
+        log.debug("{} No cached response found, resolving the response JWT from the context", getLogPrefix());
         final EntityStatementContext entityStatementContext =
                 entityStatementContextLookupStrategy.apply(profileRequestContext);
         if (entityStatementContext == null) {
             log.error("{} Could not resolve entity statement context", getLogPrefix());
             ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
-            return false;
+            return;
         }
         if (entityStatementContext.getJWT() instanceof SignedJWT signedJwt) {
             jwt = signedJwt;
         } else {
             log.error("{} No signed JWT found from the entity statement context", getLogPrefix());
             ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
-            return false;
+            return;
         }
 
-        return true;
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
-        final EntityStatement entityStatement;
+        assert jwt != null;
+        final FederationEntityConfigurationSuccessResponse response;
         try {
-            entityStatement = EntityStatement.parse(jwt);
+            response = new FederationEntityConfigurationSuccessResponse(EntityStatement.parse(jwt));
         } catch (ParseException e) {
-            log.error("{} Could not parse entity statement from JWT", getLogPrefix(), e);
+            log.error("{} No signed JWT found from the entity statement context", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+            return;
+        }
+        final NimbusResponseCriterion responseCriterion = new NimbusResponseCriterion(response);
+        final Duration lifetime = cachedResponseLifetimeLookupStrategy.apply(profileRequestContext);
+        if (lifetime == null) {
+            log.error("{} Could not resolve lifetime for the cached response record", getLogPrefix());
             ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
             return;
         }
-        final Duration lifetime = storageRecordLifetimeLookupStrategy.apply(profileRequestContext);
-        final FederationEntityConfigurationSuccessResponse response =
-                new FederationEntityConfigurationSuccessResponse(entityStatement);
+        final Instant expiration = Instant.now().plus(lifetime);
+        assert expiration != null;
+        final ResponseContainerExpirationCriterion expirationCriterion =
+                new ResponseContainerExpirationCriterion(expiration);
+        final CriteriaSet criteria = new CriteriaSet(responseCriterion, expirationCriterion);
         try {
-            assert jwt != null;
-            final String serializedJwt = jwt.serialize();
-            assert serializedJwt != null;
-            storageService.create(OIDFederationEntityConfigurationProfileConfiguration.PROFILE_ID, "entityStatement", serializedJwt, Instant.now().plus(lifetime).toEpochMilli());
-        } catch (final IOException e) {
-            log.warn("{} Could not store the entity statement into storage service", getLogPrefix(), e);
+            final List<NimbusResponseContainer> result = responseCache.get(criteria);
+            if (result.size() != 1) {
+                log.error("{} Unexpected result (size={}) when storing response record into the metadata cache",
+                        getLogPrefix(), result.size());
+            } else {
+                log.debug("{} Response stored into the cache", getLogPrefix());
+            }
+        } catch (final MetadataCacheException e) {
+            log.error("{} Could not store the response record into tht metadata cache", getLogPrefix(), e);
         }
-        log.debug("{} Response stored into the cache", getLogPrefix());
+
         profileRequestContext.ensureOutboundMessageContext().setMessage(response);
     }
 }
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/FormOutboundResolveEntityResponse.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/FormOutboundResolveEntityResponse.java
index ccdc1d8b..86cd0b89 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/FormOutboundResolveEntityResponse.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/FormOutboundResolveEntityResponse.java
@@ -33,13 +33,13 @@ import com.nimbusds.jwt.SignedJWT;
 import com.nimbusds.oauth2.sdk.Response;
 import com.nimbusds.openid.connect.sdk.federation.entities.EntityStatement;
 
-import net.shibboleth.idp.plugin.oidc.op.oidfed.config.ResolveEntityCachedSuccessResponseLifetimeLookupFunction;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.config.CachedSuccessResponseLifetimeLookupFunction;
 import net.shibboleth.idp.plugin.oidc.op.oidfed.messaging.impl.ResolveEntityRequest;
 import net.shibboleth.idp.plugin.oidc.op.oidfed.messaging.impl.ResolveEntityResponse;
-import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.ResolveEntityContainerExpirationCriterion;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.ResponseContainerExpirationCriterion;
 import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.ResolveEntityRequestCriterion;
 import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.ResolveEntityResponseContainer;
-import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.ResolveEntityResponseCriterion;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.NimbusResponseCriterion;
 import net.shibboleth.idp.profile.AbstractProfileAction;
 import net.shibboleth.oidc.metadata.cache.MetadataCache;
 import net.shibboleth.oidc.metadata.cache.MetadataCacheException;
@@ -67,7 +67,7 @@ public class FormOutboundResolveEntityResponse extends AbstractProfileAction {
 
     /** Strategy used to locate the resolve entity context. */
     @Nonnull
-    private Function<ProfileRequestContext, RelyingPartyResolveEntityContext> resolveEntityContextLookupStrategy;
+    private Function<ProfileRequestContext, RelyingPartyCachedMessageContext> cachedMessageContextLookupStrategy;
 
     /** Strategy used to locate the subcontext to hold the statement. */
     @Nonnull private Function<ProfileRequestContext,EntityStatementContext> entityStatementContextLookupStrategy;
@@ -79,7 +79,7 @@ public class FormOutboundResolveEntityResponse extends AbstractProfileAction {
     @NonnullBeforeExec private SignedJWT jwt;
 
     /** The resolve entity context to operate on. */
-    @NonnullBeforeExec private RelyingPartyResolveEntityContext resolveEntityContext;
+    @NonnullBeforeExec private RelyingPartyCachedMessageContext cachedMessageContext;
 
     /**
      * Constructor.
@@ -90,12 +90,12 @@ public class FormOutboundResolveEntityResponse extends AbstractProfileAction {
                         new OutboundMessageContextLookup());
         assert escls != null;
         entityStatementContextLookupStrategy = escls;
-        final Function<ProfileRequestContext, RelyingPartyResolveEntityContext> recls =
-                new ChildContextLookup<>(RelyingPartyResolveEntityContext.class).compose(
+        final Function<ProfileRequestContext, RelyingPartyCachedMessageContext> recls =
+                new ChildContextLookup<>(RelyingPartyCachedMessageContext.class).compose(
                         new InboundMessageContextLookup());
         assert recls != null;
-        resolveEntityContextLookupStrategy = recls;
-        cachedResponseLifetimeLookupStrategy = new ResolveEntityCachedSuccessResponseLifetimeLookupFunction();
+        cachedMessageContextLookupStrategy = recls;
+        cachedResponseLifetimeLookupStrategy = new CachedSuccessResponseLifetimeLookupFunction();
     }
 
     /**
@@ -110,14 +110,14 @@ public class FormOutboundResolveEntityResponse extends AbstractProfileAction {
     }
 
     /**
-     * Set the strategy used to locate the resolve entity context
+     * Set the strategy used to locate the cached message context
      * 
      * @param strategy What to set.
      */
-    public void setResolveEntityContextLookupStrategy(
-            @Nonnull final Function<ProfileRequestContext,RelyingPartyResolveEntityContext> strategy) {
+    public void setCachedMessageContextLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext,RelyingPartyCachedMessageContext> strategy) {
         checkSetterPreconditions();
-        resolveEntityContextLookupStrategy = Constraint.isNotNull(strategy, "Lookup strategy cannot be null!");
+        cachedMessageContextLookupStrategy = Constraint.isNotNull(strategy, "Lookup strategy cannot be null!");
     }
 
     /**
@@ -158,9 +158,9 @@ public class FormOutboundResolveEntityResponse extends AbstractProfileAction {
         if (!super.doPreExecute(profileRequestContext)) {
             return false;
         }
-        resolveEntityContext = resolveEntityContextLookupStrategy.apply(profileRequestContext);
-        if (resolveEntityContext == null) {
-            log.error("{} Could not resolve resolve entity context", getLogPrefix());
+        cachedMessageContext = cachedMessageContextLookupStrategy.apply(profileRequestContext);
+        if (cachedMessageContext == null) {
+            log.error("{} Could not resolve cached message context", getLogPrefix());
             ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
             return false;
         }
@@ -171,7 +171,7 @@ public class FormOutboundResolveEntityResponse extends AbstractProfileAction {
     /** {@inheritDoc} */
     @Override
     protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
-        final Response cachedResponse = resolveEntityContext.getCachedResponse();
+        final Response cachedResponse = cachedMessageContext.getCachedResponse();
         if (cachedResponse != null) {
             log.debug("{} Cached response found, storing in to the outbound message context", getLogPrefix());
             profileRequestContext.ensureOutboundMessageContext().setMessage(cachedResponse);
@@ -195,8 +195,7 @@ public class FormOutboundResolveEntityResponse extends AbstractProfileAction {
 
         assert jwt != null;
         final ResolveEntityResponse response = new ResolveEntityResponse(jwt);
-        final ResolveEntityResponseCriterion responseCriterion = new ResolveEntityResponseCriterion(response);
-        final ResolveEntityRequest validatedRequest = resolveEntityContext.getValidatedRequest();
+        final NimbusResponseCriterion responseCriterion = new NimbusResponseCriterion(response);
         final Duration lifetime = cachedResponseLifetimeLookupStrategy.apply(profileRequestContext);
         if (lifetime == null) {
             log.error("{} Could not resolve lifetime for the cached response record", getLogPrefix());
@@ -205,26 +204,28 @@ public class FormOutboundResolveEntityResponse extends AbstractProfileAction {
         }
         final Instant expiration = Instant.now().plus(lifetime);
         assert expiration != null;
-        final ResolveEntityContainerExpirationCriterion expirationCriterion =
-                new ResolveEntityContainerExpirationCriterion(expiration);
-        if (validatedRequest == null) {
+        final ResponseContainerExpirationCriterion expirationCriterion =
+                new ResponseContainerExpirationCriterion(expiration);
+        if (cachedMessageContext.getValidatedRequest() instanceof ResolveEntityRequest validatedRequest) {
+            final ResolveEntityRequestCriterion requestCriterion = new ResolveEntityRequestCriterion(validatedRequest);
+            final CriteriaSet criteria = new CriteriaSet(requestCriterion, responseCriterion, expirationCriterion);
+            try {
+                final List<ResolveEntityResponseContainer> result = responseCache.get(criteria);
+                if (result.size() != 1) {
+                    log.error("{} Unexpected result (size={}) when storing response record into the metadata cache",
+                            getLogPrefix(), result.size());
+                } else {
+                    log.debug("{} Response stored into the cache", getLogPrefix());
+                }
+            } catch (final MetadataCacheException e) {
+                log.error("{} Could not store the response record into tht metadata cache", getLogPrefix(), e);
+            }
+        } else {
             log.error("{} No validated request found from the resolve entity context", getLogPrefix());
             ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
             return;
         }
-        final ResolveEntityRequestCriterion requestCriterion = new ResolveEntityRequestCriterion(validatedRequest);
-        final CriteriaSet criteria = new CriteriaSet(requestCriterion, responseCriterion, expirationCriterion);
-        try {
-            final List<ResolveEntityResponseContainer> result = responseCache.get(criteria);
-            if (result.size() != 1) {
-                log.error("{} Unexpected result (size={}) when storing response record into the metadata cache",
-                        getLogPrefix(), result.size());
-            } else {
-                log.debug("{} Response stored into the cache", getLogPrefix());
-            }
-        } catch (final MetadataCacheException e) {
-            log.error("{} Could not store the response record into tht metadata cache", getLogPrefix(), e);
-        }
+
         profileRequestContext.ensureOutboundMessageContext().setMessage(response);
     }
 }
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/LookupCachedResolveEntityRespomse.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/LookupCachedEntityConfigurationResponse.java
similarity index 55%
copy from idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/LookupCachedResolveEntityRespomse.java
copy to idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/LookupCachedEntityConfigurationResponse.java
index de2ebfbc..5b38b731 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/LookupCachedResolveEntityRespomse.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/LookupCachedEntityConfigurationResponse.java
@@ -23,12 +23,9 @@ import org.opensaml.messaging.context.navigate.ChildContextLookup;
 import org.opensaml.profile.action.ActionSupport;
 import org.opensaml.profile.action.EventIds;
 import org.opensaml.profile.context.ProfileRequestContext;
-import org.opensaml.profile.context.navigate.InboundMessageContextLookup;
 import org.slf4j.Logger;
 
-import net.shibboleth.idp.plugin.oidc.op.oidfed.messaging.impl.ResolveEntityRequest;
-import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.ResolveEntityRequestCriterion;
-import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.ResolveEntityResponseContainer;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.NimbusResponseContainer;
 import net.shibboleth.idp.profile.AbstractProfileAction;
 import net.shibboleth.oidc.metadata.cache.MetadataCache;
 import net.shibboleth.oidc.metadata.cache.MetadataCacheException;
@@ -40,53 +37,49 @@ import net.shibboleth.shared.primitive.LoggerFactory;
 import net.shibboleth.shared.resolver.CriteriaSet;
 
 /**
- * Lookup if a cached response already exists for the validated resolve entity API request. If yes, the response is
- * stored into {@link RelyingPartyResolveEntityContext} and a corresponding event ID is published.
+ * Lookup if a cached response already exists for entity configuration. If yes, the response is
+ * stored into {@link RelyingPartyCachedMessageContext} and a corresponding event ID is published.
  * 
  * @event {@link EventIds#PROCEED_EVENT_ID}
  * @event {@link EventIds#INVALID_PROFILE_CTX}
- * @event {@link OidFederationEventIds#CACHED_RESOLVE_ENTITY_RESPONSE}
+ * @event {@link OidFederationEventIds#CACHED_ENTITY_CONFIGURATION_RESPONSE}
  * 
  * @since 4.3.0
  */
-public class LookupCachedResolveEntityRespomse extends AbstractProfileAction {
+public class LookupCachedEntityConfigurationResponse extends AbstractProfileAction {
 
     /** Class logger. */
-    @Nonnull private Logger log = LoggerFactory.getLogger(LookupCachedResolveEntityRespomse.class);
+    @Nonnull private Logger log = LoggerFactory.getLogger(LookupCachedEntityConfigurationResponse.class);
 
-    /** Strategy used to locate the resolve entity context. */
+    /** Strategy used to create the cached message context. */
     @Nonnull
-    private Function<ProfileRequestContext, RelyingPartyResolveEntityContext> resolveEntityContextLookupStrategy;
+    private Function<ProfileRequestContext, RelyingPartyCachedMessageContext> cachedMessageContextCreationStrategy;
 
     /** Metadata cache for cached response containers. */
-    @NonnullAfterInit private MetadataCache<ResolveEntityResponseContainer> responseCache;
+    @NonnullAfterInit private MetadataCache<NimbusResponseContainer> responseCache;
 
-    /** Trust chain context to operate on. */
-    @NonnullBeforeExec private RelyingPartyResolveEntityContext resolveEntityContext;
-
-    /** Request message to operate on. */
-    @NonnullBeforeExec private ResolveEntityRequest validatedRequest;
+    /** Cached message context to operate on. */
+    @NonnullBeforeExec private RelyingPartyCachedMessageContext cachedMessageContext;
 
     /**
      * Constructor.
      */
-    public LookupCachedResolveEntityRespomse() {
-        final Function<ProfileRequestContext, RelyingPartyResolveEntityContext> recls =
-                new ChildContextLookup<>(RelyingPartyResolveEntityContext.class).compose(
-                        new InboundMessageContextLookup());
+    public LookupCachedEntityConfigurationResponse() {
+        final Function<ProfileRequestContext, RelyingPartyCachedMessageContext> recls =
+                new ChildContextLookup<>(RelyingPartyCachedMessageContext.class, true);
         assert recls != null;
-        resolveEntityContextLookupStrategy = recls;
+        cachedMessageContextCreationStrategy = recls;
     }
 
     /**
-     * Set the strategy used to locate the resolve entity context
+     * Set the strategy used to create the cached message context
      * 
      * @param strategy What to set.
      */
-    public void setResolveEntityContextLookupStrategy(
-            @Nonnull final Function<ProfileRequestContext,RelyingPartyResolveEntityContext> strategy) {
+    public void setCachedMessageContextCreationStrategy(
+            @Nonnull final Function<ProfileRequestContext,RelyingPartyCachedMessageContext> strategy) {
         checkSetterPreconditions();
-        resolveEntityContextLookupStrategy = Constraint.isNotNull(strategy, "Lookup strategy cannot be null!");
+        cachedMessageContextCreationStrategy = Constraint.isNotNull(strategy, "Lookup strategy cannot be null!");
     }
 
     /**
@@ -94,7 +87,7 @@ public class LookupCachedResolveEntityRespomse extends AbstractProfileAction {
      * 
      * @param cache What to set.
      */
-    public void setResponseCache(@Nonnull final MetadataCache<ResolveEntityResponseContainer> cache) {
+    public void setResponseCache(@Nonnull final MetadataCache<NimbusResponseContainer> cache) {
         checkSetterPreconditions();
         responseCache = Constraint.isNotNull(cache, "Response metadata cache cannot be null");
     }
@@ -115,33 +108,31 @@ public class LookupCachedResolveEntityRespomse extends AbstractProfileAction {
             return false;
         }
 
-        resolveEntityContext = resolveEntityContextLookupStrategy.apply(profileRequestContext);
-        if (resolveEntityContext == null || resolveEntityContext.getValidatedRequest() == null) {
-            log.error("{} Could not resolve validated resolve entity request", getLogPrefix());
+        cachedMessageContext = cachedMessageContextCreationStrategy.apply(profileRequestContext);
+        if (cachedMessageContext == null) {
+            log.error("{} Could not create cached response context", getLogPrefix());
             ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
             return false;
         }
-        validatedRequest = resolveEntityContext.getValidatedRequest();
-
+        
         return true;
     }
 
     /** {@inheritDoc} */
     @Override
     protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
-        assert validatedRequest != null;
-        final ResolveEntityRequestCriterion requestCriterion = new ResolveEntityRequestCriterion(validatedRequest);
-        final CriteriaSet criteria = new CriteriaSet(requestCriterion);
+        final CriteriaSet criteria = new CriteriaSet();
         try {
-            final List<ResolveEntityResponseContainer> result = responseCache.get(criteria);
+            final List<NimbusResponseContainer> result = responseCache.get(criteria);
             if (result.size() != 1) {
                 log.debug("{} No cached response record found from the metadata cache", getLogPrefix(), result.size());
             } else {
-                final ResolveEntityResponseContainer cachedResponse = result.get(0);
-                resolveEntityContext.setCachedResponse(cachedResponse.getResponse());
+                final NimbusResponseContainer cachedResponse = result.get(0);
+                cachedMessageContext.setCachedResponse(cachedResponse.getResponse());
                 log.debug("{} Response found from the cache, publishing event {}", getLogPrefix(),
-                        OidFederationEventIds.CACHED_RESOLVE_ENTITY_RESPONSE);
-                ActionSupport.buildEvent(profileRequestContext, OidFederationEventIds.CACHED_RESOLVE_ENTITY_RESPONSE);
+                        OidFederationEventIds.CACHED_ENTITY_CONFIGURATION_RESPONSE);
+                ActionSupport.buildEvent(profileRequestContext,
+                        OidFederationEventIds.CACHED_ENTITY_CONFIGURATION_RESPONSE);
                 return;
             }
         } catch (final MetadataCacheException e) {
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/LookupCachedResolveEntityRespomse.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/LookupCachedResolveEntityResponse.java
similarity index 77%
rename from idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/LookupCachedResolveEntityRespomse.java
rename to idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/LookupCachedResolveEntityResponse.java
index de2ebfbc..1701d69a 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/LookupCachedResolveEntityRespomse.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/LookupCachedResolveEntityResponse.java
@@ -41,7 +41,7 @@ import net.shibboleth.shared.resolver.CriteriaSet;
 
 /**
  * Lookup if a cached response already exists for the validated resolve entity API request. If yes, the response is
- * stored into {@link RelyingPartyResolveEntityContext} and a corresponding event ID is published.
+ * stored into {@link RelyingPartyCachedMessageContext} and a corresponding event ID is published.
  * 
  * @event {@link EventIds#PROCEED_EVENT_ID}
  * @event {@link EventIds#INVALID_PROFILE_CTX}
@@ -49,20 +49,20 @@ import net.shibboleth.shared.resolver.CriteriaSet;
  * 
  * @since 4.3.0
  */
-public class LookupCachedResolveEntityRespomse extends AbstractProfileAction {
+public class LookupCachedResolveEntityResponse extends AbstractProfileAction {
 
     /** Class logger. */
-    @Nonnull private Logger log = LoggerFactory.getLogger(LookupCachedResolveEntityRespomse.class);
+    @Nonnull private Logger log = LoggerFactory.getLogger(LookupCachedResolveEntityResponse.class);
 
-    /** Strategy used to locate the resolve entity context. */
+    /** Strategy used to locate the cached message context. */
     @Nonnull
-    private Function<ProfileRequestContext, RelyingPartyResolveEntityContext> resolveEntityContextLookupStrategy;
+    private Function<ProfileRequestContext, RelyingPartyCachedMessageContext> cachedMessageContextLookupStrategy;
 
     /** Metadata cache for cached response containers. */
     @NonnullAfterInit private MetadataCache<ResolveEntityResponseContainer> responseCache;
 
-    /** Trust chain context to operate on. */
-    @NonnullBeforeExec private RelyingPartyResolveEntityContext resolveEntityContext;
+    /** Cached message context to operate on. */
+    @NonnullBeforeExec private RelyingPartyCachedMessageContext cachedMessageContext;
 
     /** Request message to operate on. */
     @NonnullBeforeExec private ResolveEntityRequest validatedRequest;
@@ -70,23 +70,23 @@ public class LookupCachedResolveEntityRespomse extends AbstractProfileAction {
     /**
      * Constructor.
      */
-    public LookupCachedResolveEntityRespomse() {
-        final Function<ProfileRequestContext, RelyingPartyResolveEntityContext> recls =
-                new ChildContextLookup<>(RelyingPartyResolveEntityContext.class).compose(
+    public LookupCachedResolveEntityResponse() {
+        final Function<ProfileRequestContext, RelyingPartyCachedMessageContext> recls =
+                new ChildContextLookup<>(RelyingPartyCachedMessageContext.class).compose(
                         new InboundMessageContextLookup());
         assert recls != null;
-        resolveEntityContextLookupStrategy = recls;
+        cachedMessageContextLookupStrategy = recls;
     }
 
     /**
-     * Set the strategy used to locate the resolve entity context
+     * Set the strategy used to locate the cached message context
      * 
      * @param strategy What to set.
      */
-    public void setResolveEntityContextLookupStrategy(
-            @Nonnull final Function<ProfileRequestContext,RelyingPartyResolveEntityContext> strategy) {
+    public void setCachedMessageContextLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext,RelyingPartyCachedMessageContext> strategy) {
         checkSetterPreconditions();
-        resolveEntityContextLookupStrategy = Constraint.isNotNull(strategy, "Lookup strategy cannot be null!");
+        cachedMessageContextLookupStrategy = Constraint.isNotNull(strategy, "Lookup strategy cannot be null!");
     }
 
     /**
@@ -115,13 +115,20 @@ public class LookupCachedResolveEntityRespomse extends AbstractProfileAction {
             return false;
         }
 
-        resolveEntityContext = resolveEntityContextLookupStrategy.apply(profileRequestContext);
-        if (resolveEntityContext == null || resolveEntityContext.getValidatedRequest() == null) {
+        cachedMessageContext = cachedMessageContextLookupStrategy.apply(profileRequestContext);
+        if (cachedMessageContext == null) {
+            log.error("{} Could not resolve cached response context", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+            return false;
+        }
+        
+        if (cachedMessageContext.getValidatedRequest() instanceof ResolveEntityRequest resolveEntityRequest) {
+            validatedRequest = resolveEntityRequest;
+        } else {
             log.error("{} Could not resolve validated resolve entity request", getLogPrefix());
             ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
             return false;
         }
-        validatedRequest = resolveEntityContext.getValidatedRequest();
 
         return true;
     }
@@ -138,7 +145,7 @@ public class LookupCachedResolveEntityRespomse extends AbstractProfileAction {
                 log.debug("{} No cached response record found from the metadata cache", getLogPrefix(), result.size());
             } else {
                 final ResolveEntityResponseContainer cachedResponse = result.get(0);
-                resolveEntityContext.setCachedResponse(cachedResponse.getResponse());
+                cachedMessageContext.setCachedResponse(cachedResponse.getResponse());
                 log.debug("{} Response found from the cache, publishing event {}", getLogPrefix(),
                         OidFederationEventIds.CACHED_RESOLVE_ENTITY_RESPONSE);
                 ActionSupport.buildEvent(profileRequestContext, OidFederationEventIds.CACHED_RESOLVE_ENTITY_RESPONSE);
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/OidFederationEventIds.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/OidFederationEventIds.java
index 1f3e9f4f..2558d9fa 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/OidFederationEventIds.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/OidFederationEventIds.java
@@ -34,6 +34,12 @@ public class OidFederationEventIds {
      */
     @Nonnull @NotEmpty public static final String CACHED_RESOLVE_ENTITY_RESPONSE = "CachedResolveEntityResponseFound";
 
+    /**
+     * ID of event returned if cached entity configuration response was found and set to the context.
+     */
+    @Nonnull @NotEmpty public static final String CACHED_ENTITY_CONFIGURATION_RESPONSE =
+            "CachedEntityConfigurationResponseFound";
+
     /**
      * ID of event returned if the given trust anchor is invalid.
      */
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/RelyingPartyResolveEntityContext.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/RelyingPartyCachedMessageContext.java
similarity index 73%
rename from idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/RelyingPartyResolveEntityContext.java
rename to idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/RelyingPartyCachedMessageContext.java
index 0a86ab99..938485b2 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/RelyingPartyResolveEntityContext.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/RelyingPartyCachedMessageContext.java
@@ -19,19 +19,18 @@ import javax.annotation.Nullable;
 
 import org.opensaml.messaging.context.BaseContext;
 
+import com.nimbusds.oauth2.sdk.Request;
 import com.nimbusds.oauth2.sdk.Response;
 
-import net.shibboleth.idp.plugin.oidc.op.oidfed.messaging.impl.ResolveEntityRequest;
-
 /**
- * Subcontext carrying information for resolve entity related to a relying party.
+ * Subcontext carrying information for request and response messages related to a relying party.
  * 
  * @since 4.3.0
  */
-public final class RelyingPartyResolveEntityContext extends BaseContext {
+public final class RelyingPartyCachedMessageContext extends BaseContext {
 
-    /** Validated (possibly modified) resolve entity request. */
-    @Nullable private ResolveEntityRequest validatedRequest;
+    /** Validated (possibly modified) request message. */
+    @Nullable private Request validatedRequest;
 
     /** Cached response message. */
     @Nullable private Response cachedResponse;
@@ -41,7 +40,7 @@ public final class RelyingPartyResolveEntityContext extends BaseContext {
      * 
      * @return the validated request
      */
-    @Nullable public ResolveEntityRequest getValidatedRequest() {
+    @Nullable public Request getValidatedRequest() {
         return validatedRequest;
     }
 
@@ -51,8 +50,7 @@ public final class RelyingPartyResolveEntityContext extends BaseContext {
      * @param request the validated request
      * @return this context
      */
-    @Nonnull public RelyingPartyResolveEntityContext setValidatedRequest(
-            @Nullable final ResolveEntityRequest request) {
+    @Nonnull public RelyingPartyCachedMessageContext setValidatedRequest(@Nullable final Request request) {
         validatedRequest = request;
         return this;
     }
@@ -72,7 +70,7 @@ public final class RelyingPartyResolveEntityContext extends BaseContext {
      * @param response cached response
      * @return this context
      */
-    @Nonnull public RelyingPartyResolveEntityContext setCachedResponse(@Nullable final Response response) {
+    @Nonnull public RelyingPartyCachedMessageContext setCachedResponse(@Nullable final Response response) {
         cachedResponse = response;
         return this;
     }
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateResolveEntityRequest.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateResolveEntityRequest.java
index 846aac2a..49cc1a1a 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateResolveEntityRequest.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateResolveEntityRequest.java
@@ -29,6 +29,8 @@ import org.opensaml.profile.context.ProfileRequestContext;
 import org.opensaml.profile.context.navigate.InboundMessageContextLookup;
 import org.slf4j.Logger;
 
+import com.nimbusds.oauth2.sdk.Request;
+
 import net.shibboleth.idp.plugin.oidc.op.oidfed.messaging.impl.ResolveEntityRequest;
 import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.LocalKeyContainer;
 import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.SubjectEntityIDCriterion;
@@ -45,7 +47,7 @@ import net.shibboleth.shared.resolver.CriteriaSet;
 
 /**
  * Validates the resolve entity request against the profile configuration and stores the validated (possibly modified)
- * request into {@link RelyingPartyResolveEntityContext#setValidatedRequest(ResolveEntityRequest)}.
+ * request into {@link RelyingPartyCachedMessageContext#setValidatedRequest(Request)}.
  * 
  * @event {@link EventIds#PROCEED_EVENT_ID}
  * @event {@link EventIds#INVALID_MSG_CTX}
@@ -57,9 +59,9 @@ public class ValidateResolveEntityRequest extends AbstractProfileAction {
     /** Class logger. */
     @Nonnull private Logger log = LoggerFactory.getLogger(ValidateResolveEntityRequest.class);
 
-    /** Strategy used to create the resolve entity context. */
+    /** Strategy used to create the cached message context. */
     @Nonnull
-    private Function<ProfileRequestContext, RelyingPartyResolveEntityContext> resolveEntityContextCreationStrategy;
+    private Function<ProfileRequestContext, RelyingPartyCachedMessageContext> cachedMessageContextCreationStrategy;
 
     /** Cache containing local copies of trusted trust anchor keys. */
     @NonnullAfterInit private MetadataCache<Map<String, LocalKeyContainer>> localTrustAnchorsCache;
@@ -67,18 +69,18 @@ public class ValidateResolveEntityRequest extends AbstractProfileAction {
     /** Request message to operate on. */
     @NonnullBeforeExec private ResolveEntityRequest requestMessage;
 
-    /** Trust chain context to operate on. */
-    @NonnullBeforeExec private RelyingPartyResolveEntityContext resolveEntityContext;
+    /** Cached message context to operate on. */
+    @NonnullBeforeExec private RelyingPartyCachedMessageContext cachedMessageContext;
 
     /**
      * Constructor.
      */
     public ValidateResolveEntityRequest() {
-        final Function<ProfileRequestContext, RelyingPartyResolveEntityContext> reccs =
-                new ChildContextLookup<>(RelyingPartyResolveEntityContext.class, true).compose(
+        final Function<ProfileRequestContext, RelyingPartyCachedMessageContext> reccs =
+                new ChildContextLookup<>(RelyingPartyCachedMessageContext.class, true).compose(
                         new InboundMessageContextLookup());
         assert reccs != null;
-        resolveEntityContextCreationStrategy = reccs;
+        cachedMessageContextCreationStrategy = reccs;
     }
 
     /**
@@ -87,9 +89,9 @@ public class ValidateResolveEntityRequest extends AbstractProfileAction {
      * @param strategy creation strategy
      */
     public void setResolveEntityContextCreationStrategy(
-            @Nonnull final Function<ProfileRequestContext, RelyingPartyResolveEntityContext> strategy) {
+            @Nonnull final Function<ProfileRequestContext, RelyingPartyCachedMessageContext> strategy) {
         checkSetterPreconditions();
-        resolveEntityContextCreationStrategy = Constraint.isNotNull(strategy,
+        cachedMessageContextCreationStrategy = Constraint.isNotNull(strategy,
                 "RelyingPartyResolveEntityContext creation strategy cannot be null");
     }
 
@@ -132,8 +134,8 @@ public class ValidateResolveEntityRequest extends AbstractProfileAction {
             return false;
         }
         
-        resolveEntityContext = resolveEntityContextCreationStrategy.apply(profileRequestContext);
-        if (resolveEntityContext == null) {
+        cachedMessageContext = cachedMessageContextCreationStrategy.apply(profileRequestContext);
+        if (cachedMessageContext == null) {
             log.error("{} Unable to create resolve entity context", getLogPrefix());
             ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
             return false;
@@ -153,7 +155,7 @@ public class ValidateResolveEntityRequest extends AbstractProfileAction {
             return;
         }
         log.debug("{} The following trust anchors were validated: {}", getLogPrefix(), validatedAnchors);
-        resolveEntityContext.setValidatedRequest(
+        cachedMessageContext.setValidatedRequest(
                 new ResolveEntityRequest(requestMessage.getEndpointURI(), requestMessage.getSubject(),
                         validatedAnchors, requestMessage.getEntityTypes()));
     }
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateSelectedTrustChain.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateSelectedTrustChain.java
index 082cf41c..bd5038ff 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateSelectedTrustChain.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateSelectedTrustChain.java
@@ -72,7 +72,7 @@ public class ValidateSelectedTrustChain extends AbstractProfileAction {
 
     /** Strategy used to locate the resolve entity context. */
     @Nonnull
-    private Function<ProfileRequestContext, RelyingPartyResolveEntityContext> resolveEntityContextLookupStrategy;
+    private Function<ProfileRequestContext, RelyingPartyCachedMessageContext> resolveEntityContextLookupStrategy;
 
     /** The validated request to operate on. */
     @NonnullBeforeExec private ResolveEntityRequest validatedRequest;
@@ -86,8 +86,8 @@ public class ValidateSelectedTrustChain extends AbstractProfileAction {
                         new InboundMessageContextLookup());
         assert tcls != null;
         trustChainContextLookupStrategy = tcls;
-        final Function<ProfileRequestContext, RelyingPartyResolveEntityContext> recls =
-                new ChildContextLookup<>(RelyingPartyResolveEntityContext.class).compose(
+        final Function<ProfileRequestContext, RelyingPartyCachedMessageContext> recls =
+                new ChildContextLookup<>(RelyingPartyCachedMessageContext.class).compose(
                         new InboundMessageContextLookup());
         assert recls != null;
         resolveEntityContextLookupStrategy = recls;
@@ -121,7 +121,7 @@ public class ValidateSelectedTrustChain extends AbstractProfileAction {
      * @param strategy What to set.
      */
     public void setResolveEntityContextLookupStrategy(
-            @Nonnull final Function<ProfileRequestContext,RelyingPartyResolveEntityContext> strategy) {
+            @Nonnull final Function<ProfileRequestContext,RelyingPartyCachedMessageContext> strategy) {
         checkSetterPreconditions();
         resolveEntityContextLookupStrategy = Constraint.isNotNull(strategy, "Lookup strategy cannot be null!");
     }
@@ -143,14 +143,21 @@ public class ValidateSelectedTrustChain extends AbstractProfileAction {
             return false;
         }
 
-        final RelyingPartyResolveEntityContext resolveEntityContext =
+        final RelyingPartyCachedMessageContext cachedResponseContext =
                 resolveEntityContextLookupStrategy.apply(profileRequestContext);
-        if (resolveEntityContext == null || resolveEntityContext.getValidatedRequest() == null) {
-            log.error("{} Could not resolve validated request messaGE", getLogPrefix());
+        if (cachedResponseContext == null) {
+            log.error("{} Could not resolve cached message context", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+            return false;
+        }
+
+        if (cachedResponseContext.getValidatedRequest() instanceof ResolveEntityRequest resolveEntityRequest) {
+            validatedRequest = resolveEntityRequest;
+        } else {
+            log.error("{} Could not resolve request message", getLogPrefix());
             ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
             return false;
         }
-        validatedRequest = resolveEntityContext.getValidatedRequest();
 
         return true;
     }
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/CachedEntityStatementLifetimeLookupFunction.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/CachedEntityStatementLifetimeLookupFunction.java
deleted file mode 100644
index d8ca4f4f..00000000
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/CachedEntityStatementLifetimeLookupFunction.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Licensed 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.oidfed.profile.navigate;
-
-import java.time.Duration;
-
-import javax.annotation.Nullable;
-
-import org.opensaml.profile.context.ProfileRequestContext;
-
-import net.shibboleth.profile.config.ProfileConfiguration;
-import net.shibboleth.profile.context.RelyingPartyContext;
-import net.shibboleth.profile.context.navigate.AbstractRelyingPartyLookupFunction;
-import net.shibboleth.idp.plugin.oidc.op.oidfed.config.OIDFederationEntityConfigurationProfileConfiguration;
-
-/**
- * A function that returns
- * {@link OIDFederationEntityConfigurationProfileConfiguration#getCachedEntityStatementLifetime(ProfileRequestContext)} if such a profile
- * is available from a {@link RelyingPartyContext} obtained via a lookup function, by default a child of the
- * {@link ProfileRequestContext}.
- * 
- * <p>If a specific setting is unavailable, a null value is returned.</p>
- */
-public class CachedEntityStatementLifetimeLookupFunction extends AbstractRelyingPartyLookupFunction<Duration> {
-
-    /** {@inheritDoc} */
-    @Override
-    @Nullable public Duration apply(@Nullable final ProfileRequestContext input) {
-        final RelyingPartyContext rpc = getRelyingPartyContextLookupStrategy().apply(input);
-        if (rpc != null) {
-            final ProfileConfiguration pc = rpc.getProfileConfig();
-            if (pc instanceof OIDFederationEntityConfigurationProfileConfiguration ofec) {
-                return ofec.getCachedEntityStatementLifetime(input);
-            }
-        }
-        
-        return null;
-    }
-
-}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/entity-configuration/entity-configuration-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/entity-configuration/entity-configuration-beans.xml
index b4db60a7..080dfc5a 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/entity-configuration/entity-configuration-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/entity-configuration/entity-configuration-beans.xml
@@ -15,16 +15,38 @@
     <util:constant id="shibboleth.metrics.ProfileCounter"
         static-field="net.shibboleth.idp.plugin.oidc.op.oidfed.config.DefaultOIDFederationEntityConfigurationProfileConfiguration.PROFILE_COUNTER" />
 
-    <bean id="BuildErrorResponseFromEvent"
-        class="net.shibboleth.idp.plugin.oidc.op.profile.impl.BuildJSONErrorResponseFromEvent" scope="prototype"
-        p:defaultStatusCode="500" p:defaultCode="server_error"
-        p:httpServletResponseSupplier-ref="shibboleth.HttpServletResponseSupplier">
-        <property name="eventContextLookupStrategy">
-            <bean class="net.shibboleth.idp.profile.context.navigate.WebFlowCurrentEventLookupFunction" />
+    <bean id="shibboleth.oidfed.EntityConfigurationResponseMetadataCache" parent="shibboleth.oidc.CacheBuilder">
+        <constructor-arg>
+            <bean p:cacheId="DefaultEntityConfigurationResponseMetadataCache" parent="shibboleth.oidfed.EntityConfigurationResponseMetadataCacheBuilderSpec"
+                p:cleanupTaskInterval="PT30S"/>
+        </constructor-arg>
+    </bean>
+
+    <bean id="shibboleth.oidfed.EntityConfigurationResponseMetadataCacheBuilderSpec"
+        class="net.shibboleth.oidc.metadata.cache.impl.DynamicMetadataCacheBuilderSpec"
+        p:minCacheDuration="%{idp.oidfed.entity-configuration.maxRefreshDelay:PT1S}"
+        p:maxCacheDuration="%{idp.oidfed.entity-configuration.maxRefreshDelay:PT30S}">
+        <property name="criteriaToIdentifierStrategy">
+            <bean parent="shibboleth.Functions.Constant" c:target-ref="shibboleth.oidc.issuer" />
+        </property>
+        <property name="identifierExtractionStrategy">
+            <bean parent="shibboleth.Functions.Constant" c:target-ref="shibboleth.oidc.issuer" />
+        </property>
+        <property name="metadataExpirationTimeStrategy">
+            <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.DefaultEntityConfigurationResponseContainerExpirationTimeStrategy"/>
+        </property>
+        <property name="metadataFilterStrategy">
+            <bean parent="shibboleth.BiFunctions.Expression" c:expression="#input1"/>
+        </property>
+        <property name="fetchStrategy">
+            <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.DefaultEntityConfigurationResponseFetchingStrategy" />
         </property>
     </bean>
 
-    <alias alias="ResponseCacheStorageService" name="%{idp.oidfed.entity.configurationCache:shibboleth.StorageService}" />
+    <bean id="LookupCachedEntityConfigurationResponse"
+        class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.LookupCachedEntityConfigurationResponse"
+        scope="prototype"
+        p:responseCache-ref="shibboleth.oidfed.EntityConfigurationResponseMetadataCache" />
 
     <bean id="InitializeEntityStatementContext"
         class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.InitializeEntityStatementContext"
@@ -92,7 +114,7 @@
     </bean>
 
     <bean id="FormOutboundMessage" class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.FormOutboundFederationConfigurationResponse"
-        scope="prototype" p:storageService-ref="ResponseCacheStorageService">
+        scope="prototype" p:responseCache-ref="shibboleth.oidfed.EntityConfigurationResponseMetadataCache">
     </bean>
 
 </beans>
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/entity-configuration/entity-configuration-flow.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/entity-configuration/entity-configuration-flow.xml
index f5036f68..670f03d7 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/entity-configuration/entity-configuration-flow.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/entity-configuration/entity-configuration-flow.xml
@@ -5,30 +5,15 @@
 
     <action-state id="InitializeMandatoryContexts">
         <on-entry>
-            <set name="flowScope.transitionAfterInboundIntercept" value="'CheckIfValidCachedResponseExists'" />
+            <set name="flowScope.transitionAfterInboundIntercept" value="'LookupCachedResponse'" />
         </on-entry>
     </action-state>
 
-    <decision-state id="CheckIfValidCachedResponseExists">
-        <on-entry>
-            <set name="requestScope.storageRecord"
-                value="ResponseCacheStorageService.read(T(net.shibboleth.idp.plugin.oidc.op.oidfed.config.OIDFederationEntityConfigurationProfileConfiguration).PROFILE_ID, 'entityStatement')"/>
-            <set name="requestScope.cachedEntityStatementJwt"
-                value="storageRecord == null ? null : T(com.nimbusds.openid.connect.sdk.federation.entities.EntityStatement).parse(storageRecord.getValue())" />
-        </on-entry>
-        <if test="cachedEntityStatementJwt != null"
-            then="UseCachedEntityStatement"
-            else="InitializeEntityStatementContext" />
-    </decision-state>
-
-    <action-state id="UseCachedEntityStatement">
-        <on-entry>
-            <set name="requestScope.cachedResponseMessage"
-                value="new com.nimbusds.openid.connect.sdk.federation.config.FederationEntityConfigurationSuccessResponse(cachedEntityStatementJwt)" />
-            <evaluate expression="opensamlProfileRequestContext.ensureOutboundMessageContext().setMessage(cachedResponseMessage)"/>
-        </on-entry>
-        <evaluate expression="'proceed'"/>
-        <transition on="proceed" to="PopulateOutboundInterceptContext"/>
+    <action-state id="LookupCachedResponse">
+        <evaluate expression="LookupCachedEntityConfigurationResponse" />
+        <evaluate expression="'proceed'" />
+        <transition on="CachedEntityConfigurationResponseFound" to="BuildResponseMessage" />
+        <transition on="proceed" to="InitializeEntityStatementContext" />
     </action-state>
 
     <action-state id="InitializeEntityStatementContext">
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/resolve-entity/resolve-entity-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/resolve-entity/resolve-entity-beans.xml
index e9bf3c4f..f4737f29 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/resolve-entity/resolve-entity-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/resolve-entity/resolve-entity-beans.xml
@@ -68,8 +68,8 @@
         scope="prototype"
         p:localTrustAnchorsCache-ref="#{'%{idp.oidfed.resove-entity.LocalTrustAnchorsMetadataCache:shibboleth.oidfed.LocalTrustAnchorsMetadataCache}'.trim()}" />
 
-    <bean id="LookupCachedResolveEntityRespomse"
-        class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.LookupCachedResolveEntityRespomse"
+    <bean id="LookupCachedResolveEntityResponse"
+        class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.LookupCachedResolveEntityResponse"
         scope="prototype"
         p:responseCache-ref="shibboleth.oidfed.ResolveEntityResponseMetadataCache" />
 
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/resolve-entity/resolve-entity-flow.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/resolve-entity/resolve-entity-flow.xml
index 903e3fcc..f80f8802 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/resolve-entity/resolve-entity-flow.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/resolve-entity/resolve-entity-flow.xml
@@ -41,7 +41,7 @@
 
     <action-state id="LookupCachedResponse">
         <evaluate expression="ValidateRequest" />
-        <evaluate expression="LookupCachedResolveEntityRespomse" />
+        <evaluate expression="LookupCachedResolveEntityResponse" />
         <evaluate expression="'proceed'" />
         <transition on="CachedResolveEntityResponseFound" to="BuildResponseMessage" />
         <transition on="proceed" to="ResolveTrustChains" />
diff --git a/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/relying-party.xml b/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/relying-party.xml
index dc3a03ef..5b35615a 100644
--- a/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/relying-party.xml
+++ b/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/relying-party.xml
@@ -46,7 +46,7 @@
                 <bean parent="OAUTH2.Introspection" />
                 <bean parent="OAUTH2.Revocation" />
                 <bean parent="OAUTH2.PAR" />
-                <bean parent="OIDFED.Configuration" p:cachedEntityStatementLifetime="PT2S" />
+                <bean parent="OIDFED.Configuration" p:cachedSuccessResponseLifetime="PT2S" />
                 <bean parent="OIDFED.ResolveEntity" />
             </list>
         </property>

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


More information about the commits mailing list