Fix SPIFFE client authentication when iss claim is included (#43428)

Closes #43394

Signed-off-by: stianst <stianst@gmail.com>
This commit is contained in:
Stian Thorgersen 2025-10-14 13:20:51 +02:00 committed by GitHub
parent 468c063e27
commit 5c5905fed3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 15 additions and 41 deletions

View File

@ -19,8 +19,6 @@ import org.keycloak.provider.EnvironmentDependentProviderFactory;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.services.resources.IdentityBrokerService;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@ -60,24 +58,21 @@ public class FederatedJWTClientAuthenticator extends AbstractClientAuthenticator
return;
}
final String issuer = clientAssertionState.getToken().getIssuer() != null ?
clientAssertionState.getToken().getIssuer() :
toIssuer(clientAssertionState.getToken().getSubject());
if (issuer == null) return;
AlternativeLookupProvider lookupProvider = context.getSession().getProvider(AlternativeLookupProvider.class);
IdentityProviderModel identityProviderModel = lookupProvider.lookupIdentityProviderFromIssuer(context.getSession(), issuer);
ClientAssertionIdentityProvider identityProvider = getClientAssertionIdentityProvider(context.getSession(), identityProviderModel);
if (identityProvider == null) return;
String federatedClientId = clientAssertionState.getToken().getSubject();
ClientModel client = lookupProvider.lookupClientFromClientAttributes(
context.getSession(),
Map.of(FederatedJWTClientAuthenticator.JWT_CREDENTIAL_ISSUER_KEY, identityProviderModel.getAlias(), FederatedJWTClientAuthenticator.JWT_CREDENTIAL_SUBJECT_KEY, federatedClientId));
Map.of(FederatedJWTClientAuthenticator.JWT_CREDENTIAL_SUBJECT_KEY, federatedClientId));
if (client == null) return;
String idpAlias = client.getAttribute(FederatedJWTClientAuthenticator.JWT_CREDENTIAL_ISSUER_KEY);
IdentityProviderModel identityProviderModel = context.getSession().identityProviders().getByAlias(idpAlias);
ClientAssertionIdentityProvider identityProvider = getClientAssertionIdentityProvider(context.getSession(), identityProviderModel);
if (identityProvider == null) return;
clientAssertionState.setClient(client);
if (!PROVIDER_ID.equals(client.getClientAuthenticatorType())) return;
@ -150,15 +145,4 @@ public class FederatedJWTClientAuthenticator extends AbstractClientAuthenticator
return Profile.isFeatureEnabled(Profile.Feature.CLIENT_AUTH_FEDERATED);
}
protected static String toIssuer(String subject) {
try {
URI uri = new URI(subject);
String scheme = uri.getScheme();
String authority = uri.getRawAuthority();
return scheme != null && authority != null ? scheme + "://" + authority : null;
} catch (URISyntaxException e) {
return null;
}
}
}

View File

@ -1,18 +0,0 @@
package org.keycloak.authentication.authenticators.client;
import org.junit.Test;
import org.junit.jupiter.api.Assertions;
public class FederatedJWTClientAuthenticatorTest {
@Test
public void testToIssuer() {
Assertions.assertEquals("https://something", FederatedJWTClientAuthenticator.toIssuer("https://something/client"));
Assertions.assertEquals("spiffe://trust-domain", FederatedJWTClientAuthenticator.toIssuer("spiffe://trust-domain/the"));
Assertions.assertEquals("spiffe://trust-domain", FederatedJWTClientAuthenticator.toIssuer("spiffe://trust-domain/the/client"));
Assertions.assertNull(FederatedJWTClientAuthenticator.toIssuer("client"));
Assertions.assertNull(FederatedJWTClientAuthenticator.toIssuer("the/client"));
Assertions.assertNull(FederatedJWTClientAuthenticator.toIssuer("spiffe:/the/client"));
}
}

View File

@ -74,6 +74,14 @@ public class SpiffeClientAuthTest extends AbstractFederatedClientAuthTest {
assertFailure(null, null, jwt.getSubject(), jwt.getId(), "client_not_found", events.poll());
}
@Test
public void testWithIssClaim() {
JsonWebToken jwt = createDefaultToken();
jwt.issuer("https://nosuch");
assertSuccess(INTERNAL_CLIENT_ID, doClientGrant(jwt));
assertSuccess(INTERNAL_CLIENT_ID, jwt.getId(), "https://nosuch", EXTERNAL_CLIENT_ID, events.poll());
}
@Test
public void testReuse() {
JsonWebToken jwt = createDefaultToken();