How to use quarkus-rest-client-reactive-jackson in Keycloak SPI

288 views Asked by At

I'm building a Keycloak 23.0.3 custom provider (SPI) that adds claims to the access tokens generated by Keycloak. These claims should be obtained by performing external REST API calls. For the REST client I would like to use quarkus-rest-client-reactive-jackson because it allows creating REST clients with interfaces, which is very fast and easy. The problem is that when I try to create the client I get the error:

Uncaught server error: java.lang.NoClassDefFoundError: org/eclipse/microprofile/rest/client/RestClientBuilder

I have done some research and it seems that the dependency quarkus-rest-client-reactive-jackson is not present in the Keycloak server (that is a Quarkus)... so I have to include it.

The dependencies part of my pom.xml is the following:

    <dependencies>
        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-core</artifactId>
            <scope>provided</scope>
            <version>23.0.3</version>
        </dependency>
        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-server-spi</artifactId>
            <scope>provided</scope>
            <version>23.0.3</version>
        </dependency>
        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-server-spi-private</artifactId>
            <scope>provided</scope>
            <version>23.0.3</version>
        </dependency>
        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-services</artifactId>
            <scope>provided</scope>
            <version>23.0.3</version>
        </dependency>
        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-model-jpa</artifactId>
            <scope>provided</scope>
            <version>23.0.3</version>
        </dependency>

        <dependency>
            <groupId>jakarta.persistence</groupId>
            <artifactId>jakarta.persistence-api</artifactId>
            <scope>provided</scope>
            <version>3.1.0</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.30</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>jakarta.json.bind</groupId>
            <artifactId>jakarta.json.bind-api</artifactId>
            <version>3.0.0</version>
        </dependency>
        <dependency>
            <groupId>jakarta.json</groupId>
            <artifactId>jakarta.json-api</artifactId>
            <version>2.1.3</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/io.quarkus/quarkus-rest-client-reactive-jackson -->
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-rest-client-reactive-jackson</artifactId>
            <version>3.6.3</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

Focus on the last part, the quarkus-rest-client-reactive-jackson dependency.

The REST client I'm using (as an interface) is this one:

@Path("/my-path")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public interface TokenRestInterface{
    @POST
    @Path("/token")
    TokenDtoResponse getToken(@HeaderParam("User-Id") Integer userId, @FormParam("client_id") String clientId, @FormParam("client_secret") String clientSecret, @FormParam("grant_type") String grantType);
}

The part of the code where I try to create the REST client is the follwing:

TokenRestInterface tokenRestInterface = null;
try {
    tokenRestInterface = RestClientBuilder.newBuilder().baseUrl(new URL("http://localhost:8080/")).build(TokenRestInterface.class);
} catch (MalformedURLException e) {
    throw new RuntimeException(e);
}
TokenDtoResponse tokenDtoResponse = keycloakRestInterface.getToken(userId, CLIENT_ID, CLIENT_SECRET, "client_credentials");
return tokenDtoResponse.getAccess_token();

The error I get when the previous code gets executed is: 2023-12-19 11:47:43,443 ERROR [org.keycloak.services.error.KeycloakErrorHandler] (executor-thread-1) Uncaught server error: java.lang.NoClassDefFoundError: org/eclipse/microprofile/rest/client/RestClientBuilder.

Based on this, I suppose the exception is thrown in the line: RestClientBuilder.newBuilder()....

Note that I included both jars (my provider and the dependency jar) in the providers folder of Keycloak.

providers folder of my Keycloak server

(Note that metacontratas-keycloak-provider is the name of my project, my custom Keycloak provider)

Could you please explain to me how to solve this error? This is, do you know how to add correctly the dependency I'm using into the Keycloak service? Thanks!

1

There are 1 answers

0
Jesús Moncada Ramírez On

As I wasn't able to make quarkus-rest-client-reactive-jackson work, my solution was to use resteasy-client instead. This is, add the following to the pom.xml:

<dependency>
  <groupId>org.jboss.resteasy</groupId>
  <artifactId>resteasy-client</artifactId>
  <version>6.2.6.Final</version>
  <scope>provided</scope>
</dependency>

Add the following jars to the keycloak/providers folder:

Keycloak providers folder

And create the REST client in your provider with the following:

UriBuilder fullPath = UriBuilder.fromPath("http://localhost:8080/your-path");
ResteasyClient client = (ResteasyClient) ClientBuilder.newClient();
ResteasyWebTarget target = client.target(fullPath);
this.tokenRestInterface = target.proxy(TokenRestInterface.class);

Note that, to this day, the version of the Keycloak server must be < 23.0.0 because of this issue.

Hope it helps!