'Keycloak and RESTEasy client: ClassNotFoundException: org.jboss.resteasy.client.jaxrs.internal.proxy.ProxyBuilderImpl
I need to add a RESTEasy client to keycloak (WildFly based distribution).
- I create a new class by extending
org.keycloak.broker.provider.AbstractIdentityProviderMapper - I define the following code in its constructor
Client client = ClientBuilder.newBuilder().build();
ClientWebTarget target = (ClientWebTarget) client.target(UriBuilder.fromPath(URL_PATH));
CustomFetcher fetcher = target.proxy(CustomFetcher.class);
- I add the class to
src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderMapper
When I copy the jar to keycloak's standalone/deployments directory I get
20:55:01,522 ERROR [org.jboss.msc.service.fail] (MSC service thread 1-2) MSC000001: Failed to start service jboss.deployment.unit."keycloak-user-mapper-0.0.1-SNAPSHOT.jar".POST_MODULE: org.jboss.msc.service.StartException in service jboss.deployment.unit."keycloak-user-mapper-0.0.1-SNAPSHOT.jar".POST_MODULE: WFLYSRV0153: Failed to process phase POST_MODULE of deployment "keycloak-user-mapper-0.0.1-SNAPSHOT.jar"
at [email protected]//org.jboss.as.server.deployment.DeploymentUnitPhaseService.start(DeploymentUnitPhaseService.java:189)
at [email protected]//org.jboss.msc.service.ServiceControllerImpl$StartTask.startService(ServiceControllerImpl.java:1739)
at [email protected]//org.jboss.msc.service.ServiceControllerImpl$StartTask.execute(ServiceControllerImpl.java:1701)
at [email protected]//org.jboss.msc.service.ServiceControllerImpl$ControllerTask.run(ServiceControllerImpl.java:1559)
at [email protected]//org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35)
at [email protected]//org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:1990)
at [email protected]//org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1486)
at [email protected]//org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1377)
at java.base/java.lang.Thread.run(Thread.java:829)
Caused by: java.util.ServiceConfigurationError: org.keycloak.broker.provider.IdentityProviderMapper: Provider org.myorg.UserAttributeMapper could not be instantiated
at java.base/java.util.ServiceLoader.fail(ServiceLoader.java:582)
at java.base/java.util.ServiceLoader$ProviderImpl.newInstance(ServiceLoader.java:804)
at java.base/java.util.ServiceLoader$ProviderImpl.get(ServiceLoader.java:722)
at java.base/java.util.ServiceLoader$3.next(ServiceLoader.java:1395)
at [email protected]//org.keycloak.provider.DefaultProviderLoader.load(DefaultProviderLoader.java:60)
at [email protected]//org.keycloak.provider.ProviderManager.load(ProviderManager.java:94)
at [email protected]//org.keycloak.services.DefaultKeycloakSessionFactory.loadFactories(DefaultKeycloakSessionFactory.java:294)
at [email protected]//org.keycloak.services.DefaultKeycloakSessionFactory.deploy(DefaultKeycloakSessionFactory.java:154)
at [email protected]//org.keycloak.provider.ProviderManagerRegistry.deploy(ProviderManagerRegistry.java:42)
at [email protected]//org.keycloak.subsystem.server.extension.KeycloakProviderDeploymentProcessor.deploy(KeycloakProviderDeploymentProcessor.java:58)
at [email protected]//org.jboss.as.server.deployment.DeploymentUnitPhaseService.start(DeploymentUnitPhaseService.java:182)
... 8 more
Caused by: java.lang.RuntimeException: java.lang.ClassNotFoundException: org.jboss.resteasy.client.jaxrs.internal.proxy.ProxyBuilderImpl from [Module "org.jboss.as.server" version 18.0.4.Final from local module loader @66c92293 (finder: local module finder @332796d3 (roots: /opt/jboss/keycloak/modules,/opt/jboss/keycloak/modules/system/layers/keycloak,/opt/jboss/keycloak/modules/system/layers/base))]
at [email protected]//org.jboss.resteasy.client.jaxrs.ProxyBuilder.builder(ProxyBuilder.java:41)
at [email protected]//org.jboss.resteasy.client.jaxrs.internal.ClientWebTarget.createProxyBuilder(ClientWebTarget.java:107)
at [email protected]//org.jboss.resteasy.client.jaxrs.internal.ClientWebTarget.proxy(ClientWebTarget.java:94)
at deployment.keycloak-user-mapper-0.0.1-SNAPSHOT.jar//org.myorg.UserAttributeMapper.<init>(UserAttributeMapper.java:55)
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:490)
at java.base/java.util.ServiceLoader$ProviderImpl.newInstance(ServiceLoader.java:780)
... 17 more
Caused by: java.lang.ClassNotFoundException: org.jboss.resteasy.client.jaxrs.internal.proxy.ProxyBuilderImpl from [Module "org.jboss.as.server" version 18.0.4.Final from local module loader @66c92293 (finder: local module finder @332796d3 (roots: /opt/jboss/keycloak/modules,/opt/jboss/keycloak/modules/system/layers/keycloak,/opt/jboss/keycloak/modules/system/layers/base))]
at org.jboss.modules.ModuleClassLoader.findClass(ModuleClassLoader.java:200)
at org.jboss.modules.ConcurrentClassLoader.performLoadClassUnchecked(ConcurrentClassLoader.java:410)
at org.jboss.modules.ConcurrentClassLoader.performLoadClass(ConcurrentClassLoader.java:398)
at org.jboss.modules.ConcurrentClassLoader.loadClass(ConcurrentClassLoader.java:116)
at [email protected]//org.jboss.resteasy.client.jaxrs.ProxyBuilder.builder(ProxyBuilder.java:35)
... 25 more
Apparently Wildfly's own org.jboss.as.server module is not configured properly, so it doesn't have org.jboss.resteasy.client.jaxrs.internal.proxy.ProxyBuilderImpl on its CLASSPATH, but it exists on the my jar's CLASSPATH, because I can successfully execute
Class.forName("org.jboss.resteasy.client.jaxrs.internal.proxy.ProxyBuilderImpl")
in the constructor of my class.
WORKAROUND I added <module name="org.jboss.resteasy.resteasy-client"/> to modules/system/layers/base/org/jboss/as/server/main/module.xml and it works now, but it doesn't seem like a good solution, how do I approach this configuration?
UPDATE
I suspect the problem might be with hot redeploy, if I copy the jar to keycloak first and after it I start keycloak, no problems with the CLASSPATH exist. But if I copy the jar into standalone/deployments when keycloak is running, then ClassNotFoundException is thrown. I have tried keycloak-17.0.0.zip, keycloak-17.0.1.zip, keycloak-18.0.0.zip (WildFly based)
Here is a script to reproduce the issue. Invoke it with:
bash script.sh [17.0.0 | 17.0.1 | 18.0.0]
to deploy the jar to local keycloak (the script will download keycloak's zip archive automatically if it hasn't been downloaded previously).
(*) Parameters in square brackets are optional, default version is 18.0.0
#! /bin/bash
set -x
mkdir -p so72280865 && cd so72280865 || {
echo "Unable to create a working directory"
exit 1
}
cat <<EOF > pom.xml
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.myorg</groupId>
<artifactId>keycloak-so-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-parent</artifactId>
<version>17.0.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-server-spi</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-server-spi-private</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-services</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jaxrs</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-client</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jackson2-provider</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
EOF
mkdir -p src/main/java/org/myorg/
cat <<EOF > src/main/java/org/myorg/App.java
package org.myorg;
import org.jboss.resteasy.client.jaxrs.internal.ClientWebTarget;
import org.keycloak.broker.provider.AbstractIdentityProviderMapper;
import org.keycloak.provider.ProviderConfigProperty;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.core.UriBuilder;
import java.util.Collections;
import java.util.List;
public class App extends AbstractIdentityProviderMapper {
private static final String URL_PATH = "https://api.myip.com";
private final CustomFetcher fetcher;
public static final String[] COMPATIBLE_PROVIDERS = {ANY_PROVIDER};
protected static final List<ProviderConfigProperty> configProperties = Collections.emptyList();
public App() {
final Client client = ClientBuilder.newBuilder()
.register(new org.jboss.resteasy.plugins.providers.jackson.ResteasyJackson2Provider())
.build();
ClientWebTarget target = (ClientWebTarget) client.target(UriBuilder.fromPath(URL_PATH));
fetcher = target.proxy(CustomFetcher.class);
System.out.println(fetcher.get().toString());
}
@Path("/")
public interface CustomFetcher {
@GET String get();
}
@Override
public String[] getCompatibleProviders() {
return COMPATIBLE_PROVIDERS;
}
@Override
public String getDisplayCategory() {
return "Attribute Importer";
}
@Override
public String getDisplayType() {
return "Custom Fetcher";
}
@Override
public String getHelpText() {
return "Custom Fetcher";
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return configProperties;
}
@Override
public String getId() {
return "custom-fetcher-idp-mapper";
}
}
EOF
mkdir -p src/main/resources/META-INF/services
cat <<EOF > src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderMapper
org.myorg.App
EOF
mvn package
if [ "$?" -ne "0" ] ; then
exit 1
fi
KEYCLOAK_VERSION=${1:-18.0.0}
[ -f "keycloak-legacy-${KEYCLOAK_VERSION}.zip" ] || {
curl -LO "https://github.com/keycloak/keycloak/releases/download/${KEYCLOAK_VERSION}/keycloak-legacy-${KEYCLOAK_VERSION}.zip"
}
[ -d "keycloak-${KEYCLOAK_VERSION}" ] || {
unzip "keycloak-legacy-${KEYCLOAK_VERSION}.zip"
} && cp target/keycloak-so-demo-0.0.1-SNAPSHOT.jar "keycloak-${KEYCLOAK_VERSION}"/standalone/deployments &&
./"keycloak-${KEYCLOAK_VERSION}"/bin/standalone.sh &
trap "./keycloak-${KEYCLOAK_VERSION}/bin/jboss-cli.sh --connect --command=:shutdown" EXIT
TIMEOUT=180
while true ; do
started=$(ss -lt | grep 9990 | wc -l)
[ "$started" -eq "1" ] && break
[ "$TIMEOUT" -le "0" ] && {
echo "Timeout reached starting keycloak"
exit 1
}
TIMEOUT=$((TIMEOUT - 10))
sleep 10
done
cp target/keycloak-so-demo-0.0.1-SNAPSHOT.jar "keycloak-${KEYCLOAK_VERSION}"/standalone/deployments
sleep 10
Solution 1:[1]
The org.jboss.as.server should not have a dependency on the RESTEasy module. You need your deployment to have a dependency on it. This should happen by default if the jaxrs subsystem is defined.
If the jaxrs subsystem is not present, you can add the dependencies to your deployment with either a jboss-deployment-structure.xml or with a manifest entry. For the manifest entry simply add Dependencies: org.jboss.resteasy.resteasy-client services to your META-INF/MANIFEST.MF in your JAR. Note these are comma separated entries so if you need something like the JSON provider you'd need something like:
Dependencies: org.jboss.resteasy.resteasy-client services, org.jboss.resteasy.resteasy-json-p-provider services
The "services" part just imports the services. This may not be required in some cases.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | James R. Perkins |
