Quarkus: Keycloak Admin Client does not work with Quarkus because it depends on RestEasy 3.x

Created on 27 Dec 2019  路  18Comments  路  Source: quarkusio/quarkus

Describe the bug
Ajava.lang.InstantiationError is thrown when trying to create an instance of org.keycloak.admin.client.Keycloak programmatically.

Expected behavior
The org.keycloak.admin.client.Keycloak instance is created with no exceptions or errors.

Actual behavior
The following stack trace occurs:

20:47:19 ERROR [io.undertow.request.io] Exception handling request dba6821d-e797-4a15-8a77-c767e55943b2-1 to /api/v1/tests/accounts/create: java.lang.InstantiationError: org.jboss.resteasy.spi.ResteasyProviderFactory
    at org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder.getProviderFactory(ResteasyClientBuilder.java:382)
    at org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder.register(ResteasyClientBuilder.java:497)
    at org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder.register(ResteasyClientBuilder.java:38)
    at net.myapp.security.keycloak.KeycloakClientFactory.createKeycloakClient(KeycloakClientFactory.java:92)
    at net.myapp.security.keycloak.KeycloakAdminClient.init(KeycloakAdminClient.java:58)
    at net.myapp.security.keycloak.KeycloakAdminClient_Bean.create(KeycloakAdminClient_Bean.zig:80)
    at net.myapp.security.keycloak.KeycloakAdminClient_Bean.get(KeycloakAdminClient_Bean.zig:202)
    at net.myapp.security.keycloak.KeycloakAdminClient_Bean.get(KeycloakAdminClient_Bean.zig:61)
    at net.myapp.rest.services.test.TestRS_Bean.create(TestRS_Bean.zig:320)
    at net.myapp.rest.services.test.TestRS_Bean.create(TestRS_Bean.zig:179)
    at io.quarkus.arc.impl.AbstractSharedContext.createInstanceHandle(AbstractSharedContext.java:80)
    at io.quarkus.arc.impl.ComputingCache$CacheFunction.lambda$apply$0(ComputingCache.java:99)
    at io.quarkus.arc.impl.LazyValue.get(LazyValue.java:26)
    at io.quarkus.arc.impl.ComputingCache.getValue(ComputingCache.java:41)
    at io.quarkus.arc.impl.AbstractSharedContext.get(AbstractSharedContext.java:25)
    at net.myapp.rest.services.test.TestRS_Bean.get(TestRS_Bean.zig:163)
    at net.myapp.rest.services.test.TestRS_Bean.get(TestRS_Bean.zig:405)
    at io.quarkus.arc.impl.ArcContainerImpl.beanInstanceHandle(ArcContainerImpl.java:355)
    at io.quarkus.arc.impl.ArcContainerImpl.beanInstanceHandle(ArcContainerImpl.java:367)
    at io.quarkus.arc.impl.ArcContainerImpl$1.get(ArcContainerImpl.java:222)
    at io.quarkus.arc.impl.ArcContainerImpl$1.get(ArcContainerImpl.java:219)
    at io.quarkus.arc.runtime.ArcRecorder$2$1.create(ArcRecorder.java:79)
    at io.quarkus.resteasy.common.runtime.QuarkusConstructorInjector.construct(QuarkusConstructorInjector.java:56)
    at org.jboss.resteasy.plugins.server.resourcefactory.POJOResourceFactory.createResource(POJOResourceFactory.java:69)
    at org.jboss.resteasy.core.ResourceLocatorInvoker.createResource(ResourceLocatorInvoker.java:62)
    at org.jboss.resteasy.core.ResourceLocatorInvoker.invoke(ResourceLocatorInvoker.java:112)
    at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:477)
    at org.jboss.resteasy.core.SynchronousDispatcher.lambda$invoke$4(SynchronousDispatcher.java:252)
    at org.jboss.resteasy.core.SynchronousDispatcher.lambda$preprocess$0(SynchronousDispatcher.java:153)
    at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:363)
    at org.jboss.resteasy.core.SynchronousDispatcher.preprocess(SynchronousDispatcher.java:156)
    at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:238)
    at org.jboss.resteasy.plugins.server.servlet.ServletContainerDispatcher.service(ServletContainerDispatcher.java:249)
    at org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:60)
    at org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:55)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:590)
    at io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:74)
    at io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:63)
    at io.undertow.servlet.handlers.ServletChain$1.handleRequest(ServletChain.java:68)
    at io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36)
    at io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:133)
    at io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:57)
    at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
    at io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46)
    at io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:65)
    at io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:60)
    at io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:77)
    at io.undertow.security.handlers.NotificationReceiverHandler.handleRequest(NotificationReceiverHandler.java:50)
    at io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43)
    at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
    at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
    at io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:270)
    at io.undertow.servlet.handlers.ServletInitialHandler.access$100(ServletInitialHandler.java:59)
    at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:116)
    at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:113)
    at io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:48)
    at io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43)
    at io.quarkus.undertow.runtime.UndertowDeploymentRecorder$9$1$1.call(UndertowDeploymentRecorder.java:476)
    at io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:250)
    at io.undertow.servlet.handlers.ServletInitialHandler.access$000(ServletInitialHandler.java:59)
    at io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:82)
    at io.undertow.server.Connectors.executeRootHandler(Connectors.java:290)
    at io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:669)
    at io.quarkus.runtime.CleanableExecutor$CleaningRunnable.run(CleanableExecutor.java:224)
    at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
    at org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35)
    at org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:2011)
    at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1535)
    at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1395)
    at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:29)
    at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:29)
    at java.base/java.lang.Thread.run(Thread.java:830)
    at org.jboss.threads.JBossThread.run(JBossThread.java:479)

To Reproduce
I attempt to create a Keycloak client instance like this:

        Keycloak kc = KeycloakBuilder.builder() //
                .serverUrl(authServerURL) //
                .realm(realmName) //
                .grantType(OAuth2Constants.PASSWORD) //
                .clientId(clientID) //
                .clientSecret(clientSecret) //
                .username(adminUsername) //
                .password(adminPassword) //
                .build();

That is all that is needed to trigger the error.

Configuration
Quarkus v1.1.0
Keycloak 8.0.1

Environment (please complete the following information):
Quarkus v1.1.0
Keycloak 8.0.1

Additional context
This is needed to create Keycloak user accounts programmatically. I am able to use quarkus-oidc for programmatic authorization, but making use of the Keycloak Admin Client library fails. I have successfully used this code in non-Quarkus projects deployed on Tomcat, Wildfly, etc.

areoidc kinbug

Most helpful comment

Update: quarkus-keycloak-admin-client extension is available starting from 1.4.0.CR1

All 18 comments

I'm bumping into this same issue. The cause seems to be that the Keycloak admin client depends on RESTEasy 3 while the Quarkus RESTEasy extension uses version 4, which had some breaking changes.

@TheGeekPharaoh @aelfric FYI, quarkus-keycloak-authorization uses the keycloak client though I'm not sure if it the same one that you'd like to use. If not the same then may be there is a case for a new quarkus-keycloak-client extension, unless all these client calls can be done via the the standard oidc client protocols (they'd go to quarkus-oidc-client).
Hey Pedro @pedroigor FYI

Hi, this is not a bug, and is not an issue in Quarkus. Please open a feature request in the Keycloak project (https://issues.redhat.com/projects/KEYCLOAK/summary) to request support for RestEasy 4 in the Keycloak Admin Client.

Created an issue in the Keycloak project. You can find it here:
https://issues.redhat.com/browse/KEYCLOAK-12285

@stianst Hi Stian, thanks, let me rename this issue a bit (we also keep opening Quarkus issues to track those in Vertx, etc)

Any update on this regard?

Here is a summary of the available options:

  1. KEYCLOAK-12285 is resolved soon
  2. quarkus-keycloak-authorization ships a light-weight admin client to support the main admin operations
  3. if KC admin operations used by the users watching this issue can be expressed in terms of the standard OIDC protocols like Dynamic Registration, then shipping OIDCAdminClient which will work for most of the OIDC servers would be a preferred option as it aligns well with the quarkus-oidc strategy

KEYCLOAK-12285 Keycloak is currently in a feature freeze so we are not able to work on this in Keycloak right now. For generic use-cases around administrating Keycloak through the Keycloak Admin APIs please contact the Keycloak team, not the Quarkus team. The Keycloak admin java client library is maintained and delivered by the Keycloak team, so that is the correct place to discuss this.

quarkus-keycloak-authorization is an extension to leverage Keycloak Authorization Services, it should not be used (or extended) to support generic admin use-cases.

Dynamic client registration is an API that Quarkus OIDC extension should support.

@stianst Thanks.

Dynamic client registration is an API that Quarkus OIDC extension should support.

+1, will be good to have anyway.

@stianst well, the issue is related to Quarkus too as it's the combination of the two that doesn't work so it's normal users are complaining here first.

What is a bit of a problem is that the issue was reported as a Keycloak issue in late November and if you're saying you're in feature freeze, that means that we don't have any solution for the foreseeable future, and especially for Quarkus 1.3.

I can see 3 other similar issues in the Keycloak tracker so that's definitely something that was requested on the Keycloak side. And we had several requests on the Quarkus side in our tracker, on Zulip and on SO.

I see you closed one because it would be a breaking change so I have absolutely no idea what your plans are regarding this issue and that's concerning for the Quarkus + Keycloak adoption.

I don't know what the solution is but we need to find some way to allow Quarkus and Keycloak to work well together.

Hi All, if this is urgent for some of the users here, then using JAX-RS Client API, reusing the bean classes from the admin client library if needed (while also excluding resteasy) should offer a temp workaround. For example, given the code above, you'd initialize JAX-RS Client with the same URL value as is used for quarkus-oidc (base URL + realm). The next 4 params are likely the form params, may be in some cases it will be JSON, so you'd set a form or json media type depending on what needs to be done. It will be a hack but might help for now...

@TheGeekPharaoh we have the same problem on my company , need to create keycloak ressources programmatically , so we modify the client source to work with Quarkus 1.2 (RestEasy4 compatible) and it's work pretty well , if you are interrested i can see with our admin if we can create a gitlab public repo and share the sources. let me know.

@TheGeekPharaoh isn't the Keycloak Authz Java Client enough for you needs? This one does not use Resteasy.

Had a little play with getting the KC admin client working here: https://github.com/keycloak/keycloak/pull/6817

Works on RestEasy 3 and 4.3, but does not work on 4.4. Seems like there could be a bug in RestEasy 4.4.

@pedroigor I'm looking for administration capabilities (create users, set passwords, etc.) through Quarkus. Isn't the Authz Java Client only for authn/authz functionality?

Update: quarkus-keycloak-admin-client extension is available starting from 1.4.0.CR1

Seems as if the documentation in the "org.keycloak.admin.client.KeycloakBuilder" ist not correct:

/**
 * Provides a {@link Keycloak} client builder with the ability to customize the underlying
 * {@link ResteasyClient RESTEasy client} used to communicate with the Keycloak server.
 * <p>
 * <p>Example usage with a connection pool size of 20:</p>
 * <pre>
 *   Keycloak keycloak = KeycloakBuilder.builder()
 *     .serverUrl("https://sso.example.com/auth")
 *     .realm("realm")
 *     .username("user")
 *     .password("pass")
 *     .clientId("client")
 *     .clientSecret("secret")
 *     .resteasyClient(new ResteasyClientBuilder().connectionPoolSize(20).build())
 *     .build();
 * </pre>
*/

It has the error: "Cannot instantiate the type ResteasyClientBuilder"

@michael-schnell Take a look at the sample code below to see how you can resolve the issue by using the ResteasyClientBuilderImplementation

    Keycloak kc = KeycloakBuilder.builder()
            .serverUrl(AUTH_SERVER_BASIC_URL)
            .realm(REALM)
            .username(USERNAME)
            .password(PASSWORD)
            .clientId(CLIENT_ID)
            .resteasyClient(new ResteasyClientBuilderImpl().connectionPoolSize(10).build())
            .build();
Was this page helpful?
0 / 5 - 0 ratings

Related issues

GregJohnStewart picture GregJohnStewart  路  51Comments

quarkusbot picture quarkusbot  路  152Comments

galderz picture galderz  路  80Comments

jnizet picture jnizet  路  83Comments

gsmet picture gsmet  路  47Comments