1. Overview
Quarkus is a Java-based framework for building Jakarta EE and MicroProfile-based applications, mostly around REST services. To make accessing these easier, Quarkus provides a REST client that allows us to access such REST services using a typesafe proxy object.
When REST resources are protected, we need to authenticate ourselves. In REST/HTTP this is typically done by sending HTTP headers containing our credentials. Unfortunately, the REST client API does not contain any methods to provide security details or even HTTP headers with a request.
In this tutorial, we’ll look at accessing a protected REST service via the Quarkus (MicroProfile) REST Client. Quarkus provides a simple way to provide credentials for basic authentication: the @ClientBasicAuth annotation.
2. Setup Protected Services
Let’s first go through the setup of a protected service using Quarkus.
Let’s consider the following REST service interface:
@Path("/hello")
@RequestScoped
public interface MyService {
@GET
@Produces(TEXT_PLAIN)
String hello();
}
Then, let’s implement MyService:
public class MyServiceImpl implements MyService {
@Override
@RolesAllowed("admin")
public String hello() {
return "Hello from Quarkus REST";
}
}
The @RolesAllowed annotation is on the implementation class and not on the interface. Surprisingly, this is required since the @RolesAllowed annotation isn’t inherited from an interface. However, this may not be so bad as the role name can be considered an internal detail that should not be exposed.
By default, when we enable security, Quarkus activates the basic authentication mechanism. However, we must provide an identity store (IdentityProvider in Quarkus). Basically, we can do this by providing it on the classpath. In this case, we’ll use a simple property file-based identity store by putting the following in pom.xml:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-elytron-security-properties-file</artifactId>
</dependency>
This activates security (via its transitive dependency io.quarkus:quarkus-elytron-security) and puts the above-mentioned identity store on the classpath. We can add users with their passwords and roles to this store using properties in application.properties:
quarkus.http.auth.basic=true
quarkus.security.users.embedded.enabled=true
quarkus.security.users.embedded.plain-text=true
quarkus.security.users.embedded.users.john=secret1
quarkus.security.users.embedded.roles.john=admin,user
3. RestClientBuilder and @ClientBasicAuth
Let’s look at how we can call the protected service by using the RestClientBuilder.
Here’s a type-safe call, without using credentials:
MyService myService = RestClientBuilder.newBuilder()
.baseUri(URI.create("http://localhost:8081"))
.build(MyService.class);
myService.hello();
This creates a proxy for the REST service, which we can then invoke just as we’d invoke local classes.
One way to provide security credentials involves annotating the original Jakarta REST interface with @ClientHeaderParam. Another way is to extend from this original interface and put the annotation there. In both cases, we have to manually (via code) generate the correct header using a callback method specified by the annotation. Since we’re using basic authentication we can take advantage of the @ClientBasicAuth annotation.
To provide username/password credentials for basic authentication using @ClientBasicAuth, we create a new interface type specific to a given user. There is therefore no dynamic aspect to the username and password. Every type created this way corresponds to a specific user. This is useful when an application accesses a remote service using one or more statically defined system users.
Let’s create the interface type:
@ClientBasicAuth(username = "john", password = "secret1")
public interface MyServiceJohnRightCredentials extends MyService {
}
Subsequently, we pass in the type with credentials instead of the original type:
MyService myService = RestClientBuilder.newBuilder()
.baseUri(URI.create("http://localhost:8081"))
.build(MyServiceJohnRightCredentials.class);
myService.hello();
Using the code above the Quarkus RestClientBuilder generates the right headers to access the REST service using basic authentication. Notably, we use constants here for simplicity. In practice, we can use expressions like ${john.password} to refer to config properties.
4. Conclusion
In this article, we saw how to set up a REST service protected by basic authentication, and how to subsequently access such a service using the @ClientBasicAuthentication annotation.
We saw that using this @ClientBasicAuthentication annotation we don’t have to modify the existing REST interface. However, we also observed that we set up static (pre-defined) users in this way; @ClientBasicAuthentication does not suit dynamic input.
As usual, all code samples used in this article are available over on GitHub.