Add support for spiffe_refresh_hint to Spiffe Identity Provider (#43242)

Closes #42806

Signed-off-by: stianst <stianst@gmail.com>
This commit is contained in:
Stian Thorgersen 2025-10-07 14:00:46 +02:00 committed by GitHub
parent 9546fca45e
commit ab7939f33a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 50 additions and 4 deletions

View File

@ -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());
}
}

View File

@ -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;
}
}

View File

@ -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++;
}
}

View File

@ -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 -> {