diff --git a/services/src/main/java/org/keycloak/broker/spiffe/SpiffeBundleEndpointLoader.java b/services/src/main/java/org/keycloak/broker/spiffe/SpiffeBundleEndpointLoader.java index 3a92a48dba7..e2728eff998 100644 --- a/services/src/main/java/org/keycloak/broker/spiffe/SpiffeBundleEndpointLoader.java +++ b/services/src/main/java/org/keycloak/broker/spiffe/SpiffeBundleEndpointLoader.java @@ -1,11 +1,10 @@ package org.keycloak.broker.spiffe; import org.keycloak.crypto.PublicKeysWrapper; -import org.keycloak.jose.jwk.JSONWebKeySet; +import org.keycloak.http.simple.SimpleHttp; import org.keycloak.jose.jwk.JWK; import org.keycloak.keys.PublicKeyLoader; import org.keycloak.models.KeycloakSession; -import org.keycloak.protocol.oidc.utils.JWKSHttpUtils; import org.keycloak.util.JWKSUtils; public class SpiffeBundleEndpointLoader implements PublicKeyLoader { @@ -20,8 +19,9 @@ public class SpiffeBundleEndpointLoader implements PublicKeyLoader { @Override public PublicKeysWrapper loadKeys() throws Exception { - JSONWebKeySet jwks = JWKSHttpUtils.sendJwksRequest(session, bundleEndpoint); - return JWKSUtils.getKeyWrappersForUse(jwks, JWK.Use.JWT_SVID); + SpiffeJSONWebKeySet jwks = SimpleHttp.create(session).doGet(bundleEndpoint).asJson(SpiffeJSONWebKeySet.class); + PublicKeysWrapper keysWrapper = JWKSUtils.getKeyWrappersForUse(jwks, JWK.Use.JWT_SVID); + return jwks.getSpiffeRefreshHint() == null ? keysWrapper : new PublicKeysWrapper(keysWrapper.getKeys(), jwks.getSpiffeRefreshHint()); } } diff --git a/services/src/main/java/org/keycloak/broker/spiffe/SpiffeJSONWebKeySet.java b/services/src/main/java/org/keycloak/broker/spiffe/SpiffeJSONWebKeySet.java new file mode 100644 index 00000000000..1fb28c89064 --- /dev/null +++ b/services/src/main/java/org/keycloak/broker/spiffe/SpiffeJSONWebKeySet.java @@ -0,0 +1,18 @@ +package org.keycloak.broker.spiffe; + +import com.fasterxml.jackson.annotation.JsonProperty; +import org.keycloak.jose.jwk.JSONWebKeySet; + +public class SpiffeJSONWebKeySet extends JSONWebKeySet { + + @JsonProperty("spiffe_refresh_hint") + private Long spiffeRefreshHint; + + public Long getSpiffeRefreshHint() { + return spiffeRefreshHint; + } + + public void setSpiffeRefreshHint(Long spiffeRefreshHint) { + this.spiffeRefreshHint = spiffeRefreshHint; + } +} diff --git a/test-framework/oauth/src/main/java/org/keycloak/testframework/oauth/OAuthIdentityProvider.java b/test-framework/oauth/src/main/java/org/keycloak/testframework/oauth/OAuthIdentityProvider.java index 61f1f03b41b..6690021aa72 100644 --- a/test-framework/oauth/src/main/java/org/keycloak/testframework/oauth/OAuthIdentityProvider.java +++ b/test-framework/oauth/src/main/java/org/keycloak/testframework/oauth/OAuthIdentityProvider.java @@ -31,6 +31,8 @@ public class OAuthIdentityProvider { private final OAuthIdentityProviderKeys keys; private final OAuthIdentityProviderConfigBuilder.OAuthIdentityProviderConfiguration config; + private int keysRequestCount = 0; + public OAuthIdentityProvider(HttpServer httpServer, OAuthIdentityProviderConfigBuilder.OAuthIdentityProviderConfiguration config) { this.config = config; if (!CryptoIntegration.isInitialised()) { @@ -55,6 +57,10 @@ public class OAuthIdentityProvider { return new OAuthIdentityProviderKeys(config); } + public int getKeysRequestCount() { + return keysRequestCount; + } + public void close() { httpServer.removeContext("/idp/jwks"); } @@ -68,6 +74,8 @@ public class OAuthIdentityProvider { OutputStream outputStream = exchange.getResponseBody(); outputStream.write(keys.getJwksString().getBytes(StandardCharsets.UTF_8)); outputStream.close(); + + keysRequestCount++; } } diff --git a/tests/base/src/test/java/org/keycloak/tests/client/authentication/external/SpiffeClientAuthTest.java b/tests/base/src/test/java/org/keycloak/tests/client/authentication/external/SpiffeClientAuthTest.java index ba02cab3ce7..f75f32d616a 100644 --- a/tests/base/src/test/java/org/keycloak/tests/client/authentication/external/SpiffeClientAuthTest.java +++ b/tests/base/src/test/java/org/keycloak/tests/client/authentication/external/SpiffeClientAuthTest.java @@ -1,5 +1,6 @@ package org.keycloak.tests.client.authentication.external; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; @@ -20,6 +21,8 @@ import org.keycloak.testframework.oauth.annotations.InjectOAuthIdentityProvider; import org.keycloak.testframework.realm.ManagedRealm; import org.keycloak.testframework.realm.RealmConfig; import org.keycloak.testframework.realm.RealmConfigBuilder; +import org.keycloak.testframework.remote.timeoffset.InjectTimeOffSet; +import org.keycloak.testframework.remote.timeoffset.TimeOffSet; import org.keycloak.testframework.server.KeycloakServerConfigBuilder; import org.keycloak.testsuite.util.IdentityProviderBuilder; @@ -39,10 +42,27 @@ public class SpiffeClientAuthTest extends AbstractFederatedClientAuthTest { @InjectOAuthIdentityProvider(config = SpiffeIdpConfig.class) OAuthIdentityProvider identityProvider; + @InjectTimeOffSet + TimeOffSet timeOffSet; + public SpiffeClientAuthTest() { super(null, INTERNAL_CLIENT_ID, EXTERNAL_CLIENT_ID); } + @Test + public void testKeysCached() { + int initialKeyRequests = identityProvider.getKeysRequestCount(); + Assertions.assertTrue(doClientGrant(createDefaultToken()).isSuccess()); + Assertions.assertTrue(doClientGrant(createDefaultToken()).isSuccess()); + Assertions.assertEquals(initialKeyRequests + 1, identityProvider.getKeysRequestCount()); + + timeOffSet.set(350); + + Assertions.assertTrue(doClientGrant(createDefaultToken()).isSuccess()); + Assertions.assertTrue(doClientGrant(createDefaultToken()).isSuccess()); + Assertions.assertEquals(initialKeyRequests + 2, identityProvider.getKeysRequestCount()); + } + @Test public void testInvalidTrustDomain() { realm.updateIdentityProviderWithCleanup(IDP_ALIAS, rep -> {