Skip to content

Commit

Permalink
Merge pull request #43260 from pskopek/kc-client-libs
Browse files Browse the repository at this point in the history
Use keycloak-client libraries instead of keycloak-common, keycloak-core and keycloak-adapter-spi
  • Loading branch information
geoand authored Dec 23, 2024
2 parents 5f730df + 27d6bc2 commit f253d80
Show file tree
Hide file tree
Showing 21 changed files with 103 additions and 233 deletions.
32 changes: 8 additions & 24 deletions bom/application/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@
<mockito.version>5.14.2</mockito.version>
<jna.version>5.8.0</jna.version><!-- should satisfy both testcontainers and mongodb -->
<quarkus-security.version>2.2.0</quarkus-security.version>
<keycloak.version>25.0.6</keycloak.version>
<keycloak-client.version>26.0.3</keycloak-client.version>
<logstash-gelf.version>1.15.1</logstash-gelf.version>
<checker-qual.version>3.48.3</checker-qual.version>
<error-prone-annotations.version>2.36.0</error-prone-annotations.version>
Expand Down Expand Up @@ -6110,41 +6110,25 @@
<version>${quarkus-spring-boot-api.version}</version>
</dependency>

<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>
<version>${keycloak.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-common</artifactId>
<version>${keycloak.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-admin-client</artifactId>
<version>${keycloak.version}</version>
<exclusions>
<exclusion>
<groupId>org.checkerframework</groupId>
<artifactId>checker-qual</artifactId>
</exclusion>
</exclusions>
<version>${keycloak-client.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-adapter-spi</artifactId>
<version>${keycloak.version}</version>
<artifactId>keycloak-authz-client</artifactId>
<version>${keycloak-client.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-authz-client</artifactId>
<version>${keycloak.version}</version>
<artifactId>keycloak-policy-enforcer</artifactId>
<version>${keycloak-client.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-policy-enforcer</artifactId>
<version>${keycloak.version}</version>
<artifactId>keycloak-client-common-synced</artifactId>
<version>${keycloak-client.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
Expand Down
5 changes: 2 additions & 3 deletions build-parent/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,9 @@
<junit4.version>4.13.2</junit4.version>

<!-- The image to use for tests that run Keycloak -->
<!-- IMPORTANT: If this is changed you must also update bom/application/pom.xml and KeycloakBuildTimeConfig/DevServicesConfig in quarkus-oidc/deployment to match the version -->
<keycloak.version>25.0.6</keycloak.version>
<keycloak.server.version>25.0.6</keycloak.server.version>
<keycloak.wildfly.version>19.0.3</keycloak.wildfly.version>
<keycloak.docker.image>quay.io/keycloak/keycloak:${keycloak.version}</keycloak.docker.image>
<keycloak.docker.image>quay.io/keycloak/keycloak:${keycloak.server.version}</keycloak.docker.image>
<keycloak.docker.legacy.image>quay.io/keycloak/keycloak:${keycloak.wildfly.version}-legacy</keycloak.docker.legacy.image>

<unboundid-ldap.version>7.0.2</unboundid-ldap.version>
Expand Down
8 changes: 1 addition & 7 deletions extensions/devservices/keycloak/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,7 @@
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>
<exclusions>
<exclusion>
<groupId>com.sun.activation</groupId>
<artifactId>jakarta.activation</artifactId>
</exclusion>
</exclusions>
<artifactId>keycloak-client-common-synced</artifactId>
</dependency>
</dependencies>
<build>
Expand Down
10 changes: 0 additions & 10 deletions extensions/keycloak-admin-rest-client/runtime/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,6 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-tls-registry</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>
<exclusions>
<exclusion>
<groupId>com.sun.activation</groupId>
<artifactId>jakarta.activation</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.eclipse.angus</groupId>
<artifactId>angus-activation</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,25 @@

import javax.net.ssl.SSLContext;

import jakarta.enterprise.inject.Instance;
import jakarta.ws.rs.Priorities;
import jakarta.ws.rs.client.Client;
import jakarta.ws.rs.client.WebTarget;
import jakarta.ws.rs.core.MediaType;

import org.eclipse.microprofile.config.ConfigProvider;
import org.jboss.resteasy.reactive.client.TlsConfig;
import org.jboss.resteasy.reactive.client.api.ClientLogger;
import org.jboss.resteasy.reactive.client.impl.ClientBuilderImpl;
import org.jboss.resteasy.reactive.client.impl.WebTargetImpl;
import org.jboss.resteasy.reactive.server.jackson.JacksonBasicMessageBodyReader;
import org.keycloak.admin.client.spi.ResteasyClientProvider;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;

import io.quarkus.arc.Arc;
import io.quarkus.arc.ArcContainer;
import io.quarkus.arc.InstanceHandle;
import io.quarkus.jackson.ObjectMapperCustomizer;
import io.quarkus.rest.client.reactive.jackson.runtime.serialisers.ClientJacksonMessageBodyWriter;
import io.quarkus.tls.TlsConfiguration;
import io.vertx.core.net.KeyCertOptions;
Expand Down Expand Up @@ -69,38 +68,13 @@ private ClientBuilderImpl registerJacksonProviders(ClientBuilderImpl clientBuild
if (arcContainer == null) {
throw new IllegalStateException(this.getClass().getName() + " should only be used in a Quarkus application");
} else {
InstanceHandle<ObjectMapper> objectMapperInstance = arcContainer.instance(ObjectMapper.class);
boolean canReuseObjectMapper = canReuseObjectMapper(objectMapperInstance, arcContainer);
if (canReuseObjectMapper) {

ObjectMapper objectMapper = null;

InstanceHandle<JacksonBasicMessageBodyReader> readerInstance = arcContainer
.instance(JacksonBasicMessageBodyReader.class);
if (readerInstance.isAvailable()) {
clientBuilder = clientBuilder.register(readerInstance.get());
} else {
objectMapper = getObjectMapper(objectMapper, objectMapperInstance);
clientBuilder = clientBuilder.register(new JacksonBasicMessageBodyReader(objectMapper));
}

InstanceHandle<ClientJacksonMessageBodyWriter> writerInstance = arcContainer
.instance(ClientJacksonMessageBodyWriter.class);
if (writerInstance.isAvailable()) {
clientBuilder = clientBuilder.register(writerInstance.get());
} else {
objectMapper = getObjectMapper(objectMapper, objectMapperInstance);
clientBuilder = clientBuilder.register(new ClientJacksonMessageBodyWriter(objectMapper));
}
} else {
ObjectMapper newObjectMapper = new ObjectMapper();
clientBuilder = clientBuilder
.registerMessageBodyReader(new JacksonBasicMessageBodyReader(newObjectMapper), Object.class,
HANDLED_MEDIA_TYPES, true,
READER_PROVIDER_PRIORITY)
.registerMessageBodyWriter(new ClientJacksonMessageBodyWriter(newObjectMapper), Object.class,
HANDLED_MEDIA_TYPES, true, WRITER_PROVIDER_PRIORITY);
}
ObjectMapper newObjectMapper = newKeycloakAdminClientObjectMapper();
clientBuilder = clientBuilder
.registerMessageBodyReader(new JacksonBasicMessageBodyReader(newObjectMapper), Object.class,
HANDLED_MEDIA_TYPES, true,
READER_PROVIDER_PRIORITY)
.registerMessageBodyWriter(new ClientJacksonMessageBodyWriter(newObjectMapper), Object.class,
HANDLED_MEDIA_TYPES, true, WRITER_PROVIDER_PRIORITY);
InstanceHandle<ClientLogger> clientLogger = arcContainer.instance(ClientLogger.class);
if (clientLogger.isAvailable()) {
clientBuilder.clientLogger(clientLogger.get());
Expand All @@ -109,36 +83,14 @@ private ClientBuilderImpl registerJacksonProviders(ClientBuilderImpl clientBuild
return clientBuilder;
}

// the idea is to only reuse the ObjectMapper if no known customizations would break Keycloak
// TODO: in the future we could also look into checking the ObjectMapper bean itself to see how it has been configured
private boolean canReuseObjectMapper(InstanceHandle<ObjectMapper> objectMapperInstance, ArcContainer arcContainer) {
if (objectMapperInstance.isAvailable() && !objectMapperInstance.getBean().isDefaultBean()) {
// in this case a user provided a completely custom ObjectMapper, so we can't use it
return false;
}

Instance<ObjectMapperCustomizer> customizers = arcContainer.beanManager().createInstance()
.select(ObjectMapperCustomizer.class);
if (!customizers.isUnsatisfied()) {
// ObjectMapperCustomizer can make arbitrary changes, so in order to be safe we won't allow reuse
return false;
}
// if any Jackson properties were configured, disallow reuse - this is done in order to provide forward compatibility with new Jackson configuration options
for (String propertyName : ConfigProvider.getConfig().getPropertyNames()) {
if (propertyName.startsWith("quarkus.jackson")) {
return false;
}
}
return true;
}

// the whole idea here is to reuse the ObjectMapper instance
private ObjectMapper getObjectMapper(ObjectMapper value,
InstanceHandle<ObjectMapper> objectMapperInstance) {
if (value == null) {
return objectMapperInstance.isAvailable() ? objectMapperInstance.get() : new ObjectMapper();
}
return value;
// creates new ObjectMapper compatible with Keycloak Admin Client
private ObjectMapper newKeycloakAdminClientObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
// Same like JSONSerialization class. Makes it possible to use admin-client against older versions of Keycloak server where the properties on representations might be different
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
// The client must work with the newer versions of Keycloak server, which might contain the JSON fields not yet known by the client. So unknown fields will be ignored.
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return objectMapper;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@

import io.quarkus.arc.BeanDestroyer;
import io.quarkus.arc.deployment.SyntheticBeanBuildItem;
import io.quarkus.deployment.Capabilities;
import io.quarkus.deployment.Capability;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.annotations.ExecutionTime;
Expand Down Expand Up @@ -58,11 +56,8 @@ void avoidRuntimeInitIssueInClientBuilderWrapper(ResteasyKeycloakAdminClientReco
@Record(ExecutionTime.RUNTIME_INIT)
@Produce(ServiceStartBuildItem.class)
@BuildStep
public void integrate(ResteasyKeycloakAdminClientRecorder recorder, Capabilities capabilities,
TlsRegistryBuildItem tlsRegistryBuildItem) {
boolean areJSONBProvidersPresent = capabilities.isPresent(Capability.RESTEASY_JSON_JSONB)
|| capabilities.isPresent(Capability.RESTEASY_JSON_JSONB_CLIENT);
recorder.setClientProvider(areJSONBProvidersPresent, tlsRegistryBuildItem.registry());
public void integrate(ResteasyKeycloakAdminClientRecorder recorder, TlsRegistryBuildItem tlsRegistryBuildItem) {
recorder.setClientProvider(tlsRegistryBuildItem.registry());
}

@Record(ExecutionTime.RUNTIME_INIT)
Expand Down
10 changes: 0 additions & 10 deletions extensions/keycloak-admin-resteasy-client/runtime/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,6 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-client-jaxb</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>
<exclusions>
<exclusion>
<groupId>com.sun.activation</groupId>
<artifactId>jakarta.activation</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.eclipse.angus</groupId>
<artifactId>angus-activation</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
import org.keycloak.admin.client.KeycloakBuilder;
import org.keycloak.admin.client.spi.ResteasyClientProvider;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;

import io.quarkus.keycloak.admin.client.common.KeycloakAdminClientConfig;
import io.quarkus.resteasy.common.runtime.jackson.QuarkusJacksonSerializer;
import io.quarkus.runtime.RuntimeValue;
Expand Down Expand Up @@ -66,7 +70,7 @@ public Keycloak get() {
};
}

public void setClientProvider(boolean areJSONBProvidersPresent, Supplier<TlsConfigurationRegistry> registrySupplier) {
public void setClientProvider(Supplier<TlsConfigurationRegistry> registrySupplier) {
var registry = registrySupplier.get();
var namedTlsConfig = TlsConfiguration.from(registry,
keycloakAdminClientConfigRuntimeValue.getValue().tlsConfigurationName()).orElse(null);
Expand Down Expand Up @@ -100,12 +104,10 @@ public Client newRestEasyClient(Object customJacksonProvider, SSLContext sslCont
}
}

// point here is to use default Quarkus providers rather than org.keycloak.admin.client.JacksonProvider
// as it doesn't work properly in native mode
if (areJSONBProvidersPresent) {
// when both Jackson and JSONB providers are present, we need to ensure Jackson is used
builder.register(new AppJsonQuarkusJacksonSerializer(), 100);
}
// this ensures we don't customize managed (shared) ObjectMapper available in the CDI container
// and that we use QuarkusJacksonSerializer that works in native mode
builder.register(new AppJsonQuarkusJacksonSerializer(), 100);

return builder.build();
}

Expand All @@ -126,6 +128,24 @@ public void avoidRuntimeInitIssueInClientBuilderWrapper() {
// makes media type more specific which ensures that it will be used first
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
static class AppJsonQuarkusJacksonSerializer extends QuarkusJacksonSerializer {
static final class AppJsonQuarkusJacksonSerializer extends QuarkusJacksonSerializer {

private final ObjectMapper objectMapper;

private AppJsonQuarkusJacksonSerializer() {
this.objectMapper = new ObjectMapper();
// Same like JSONSerialization class. Makes it possible to use admin-client against older
// versions of Keycloak server where the properties on representations might be different
this.objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
// The client must work with the newer versions of Keycloak server, which might contain the JSON fields
// not yet known by the client. So unknown fields will be ignored.
this.objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}

@Override
public ObjectMapper locateMapper(Class<?> type, MediaType mediaType) {
return objectMapper;
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import org.keycloak.adapters.authorization.cip.spi.ClaimInformationPointProviderFactory;
import org.keycloak.authorization.client.representation.ServerConfiguration;
import org.keycloak.authorization.client.representation.TokenIntrospectionResponse;
import org.keycloak.authorization.client.util.crypto.AuthzClientCryptoProvider;
import org.keycloak.common.crypto.CryptoProvider;
import org.keycloak.jose.jwk.JSONWebKeySet;
import org.keycloak.jose.jwk.JWK;
import org.keycloak.jose.jws.JWSHeader;
Expand Down Expand Up @@ -78,6 +80,8 @@ public void registerServiceProviders(BuildProducer<ServiceProviderBuildItem> ser
serviceProvider.produce(new ServiceProviderBuildItem(ClaimInformationPointProviderFactory.class.getName(),
HttpClaimInformationPointProviderFactory.class.getName(),
ClaimsInformationPointProviderFactory.class.getName()));
serviceProvider.produce(new ServiceProviderBuildItem(CryptoProvider.class.getName(),
AuthzClientCryptoProvider.class.getName()));
}

@BuildStep
Expand Down
15 changes: 0 additions & 15 deletions extensions/keycloak-authorization/runtime/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,29 +22,14 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-tls-registry</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>
<exclusions>
<exclusion>
<groupId>com.sun.activation</groupId>
<artifactId>jakarta.activation</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-policy-enforcer</artifactId>
<version>${keycloak.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.angus</groupId>
<artifactId>angus-activation</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-adapter-spi</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-authz-client</artifactId>
Expand Down
4 changes: 4 additions & 0 deletions extensions/oidc-client-reactive-filter/deployment/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</exclusion>
<exclusion>
<groupId>org.jboss.resteasy</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
Expand Down
Loading

0 comments on commit f253d80

Please sign in to comment.