Build a Java REST API With Quarkus
Learn more about building a REST API service with Quarkus.
Join the DZone community and get the full member experience.
Join For FreeQuarkus is designed as a container-first framework optimized for high speed, low memory usage, and great scalability. The container-first strategy bypasses the configuration issues and countless updates that may come along with monolithic server systems by bundling together the runtime environment and application code.
Initially, Quarkus was created to support native code for Graal/SubstrateVM, but it also works with JVM and OpenJDK HotSpot. Quarkus supports many industry-standard libraries such as Hibernate, Kubernetes, RESTEasy, and Eclipse MicroProfile.
You may also like: Quick Guide to Microservices With Quarkus on OpenShift
Quarkus was created to be utilized in microservice and serverless environments as well as reactive programming models. It uses JAX-RS for the REST endpoints, JPA to preserve data models, and CDI for dependency injections.
Through this post, you’ll learn how to use Java and Quarkus to create a REST API with JAX-RS, and secure it with OAuth 2.0 and Okta.
This tutorial is a modified and updated version of the “Quarkus — Using JWT RBAC” tutorial on the Quarkus website. The main difference is that this tutorial will use Okta as the OAuth provider and the OIDC Debugger to generate tokens for ad hoc testing (instead of rolling the whole thing yourself).
Let’s get started!
Install Quarkus Tutorial Prerequisites
You’ll need to install a few things before you get started.
Java 11: This project uses Java 11. OpenJDK 11 will work just as well. Instructions are found on the OpenJDK website. OpenJDK can also be installed using Homebrew. Alternatively, SDKMAN is another great option for installing and managing Java versions.
Just a hint, if you run mvn -v
, you’ll see your Maven version AND the Java version Maven is running on.
On my computer (a Mac), I was able to use the following command to set the shell in which Maven was running to Java 11: export JAVA_HOME=$(/usr/libexec/java_home -v 11)
.
HTTPie: This is a simple command-line utility for making HTTP requests. You’ll use this to test the REST application. Check out the installation instructions on their website.
Okta Developer Account: You’ll be using Okta as an OAuth/OIDC provider to add JWT authentication and authorization to the application. Go to our developer site and sign up for a free developer account.
Create a Java Quarkus Project
Open a terminal and cd
to an appropriate parent directory for your project. The command below uses the quarkus-maven-plugin
to create a starter application and places it in the oauthdemo
subdirectory.
mvn io.quarkus:quarkus-maven-plugin:0.23.1:create \
-DprojectGroupId=com.okta.quarkus \
-DprojectArtifactId=oauthdemo \
-DclassName="com.okta.quarkus.jwt.TokenSecuredResource" \
-Dpath="/secured" \
-Dextensions="resteasy-jsonb, jwt"
If you run the project at this point, you’ll get an error because you need to define some application properties first.
Configure Quarkus Application Properties
Open the src/main/resources/application.properties
file and copy and paste the following into it.
mp.jwt.verify.publickey.location=https://{yourOktaDomain}/oauth2/default/v1/keys
mp.jwt.verify.issuer=https://{yourOktaDomain}/oauth2/default
quarkus.smallrye-jwt.auth-mechanism=MP-JWT
quarkus.smallrye-jwt.enabled=true
You’ll need to fill in your Okta developer URI in two places.
To find your developer URI, open your Okta developer dashboard and navigate to API > Authorization Servers. Look at the row for the default
auth server where you’ll see the Issuer URI.
That domain is your Okta URI that you’ll need to populate in place of {yourOktaDomain}
.
Test the Default Quarkus Endpoint
Navigate into the project directory: cd oauthdemo
.
Run the project:
./mvnw compile quarkus:dev
You should see output like this:
[INFO] Scanning for projects...
[INFO]
[INFO] ---------------------< com.okta.quarkus:oauthdemo >---------------------
[INFO] Building oauthdemo 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
...
Listening for transport dt_socket at address: 5005
2019-09-30 10:39:02,186 INFO [io.qua.dep.QuarkusAugmentor] (main) Beginning quarkus augmentation
2019-09-30 10:39:02,889 INFO [io.qua.dep.QuarkusAugmentor] (main) Quarkus augmentation completed in 703ms
2019-09-30 10:39:03,266 INFO [io.quarkus] (main) Quarkus 0.23.1 started in 1.195s. Listening on: http://0.0.0.0:8080
2019-09-30 10:39:03,268 INFO [io.quarkus] (main) Profile dev activated. Live Coding activated.
2019-09-30 10:39:03,268 INFO [io.quarkus] (main) Installed features: [cdi, resteasy, resteasy-jsonb, security, smallrye-jwt]
If you get an error, first check which version of java Maven is using.
./mvnw -v
Now, in another terminal window, use HTTPie to test the generated endpoint:
$ http :8080/secured
HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 5
Content-Type: text/plain;charset=UTF-8
hello
Pretty sweet! But we can do a little better.
Replace the src/main/java/com/okta/quarkus/jwt/TokenSecuredResource.java
file with the following:
package com.okta.quarkus.jwt;
import java.security.Principal;
import javax.annotation.security.PermitAll;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.SecurityContext;
import org.eclipse.microprofile.jwt.JsonWebToken;
/**
* Version 1 of the TokenSecuredResource
*/
@Path("/secured")
@RequestScoped
public class TokenSecuredResource {
@Inject
JsonWebToken jwt;
@GET()
@Path("/permit-all")
@PermitAll
@Produces(MediaType.TEXT_PLAIN)
public String hello(@Context SecurityContext ctx) {
Principal caller = ctx.getUserPrincipal();
String name = caller == null ? "anonymous" : caller.getName();
boolean hasJWT = jwt != null;
String helloReply = String.format("hello + %s, isSecure: %s, authScheme: %s, hasJWT: %s", name, ctx.isSecure(), ctx.getAuthenticationScheme(), hasJWT);
return helloReply;
}
}
Now, save the file and test the new, updated endpoint:
$ http :8080/secured/permit-all
HTTP/1.1 200 OK
...
hello + anonymous, isSecure: false, authScheme: null, hasJWT: true
Did you notice how you didn’t need to re-start or re-compile your application for the new endpoint to work? That’s one of the slickest features of Quarkus!
Next, we’ll add OAuth 2.0 support to the application.
Create an OIDC Application in Okta to Test Your Quarkus Service
Head over to your Okta developer dashboard — if this is your first time logging in, you may need to click the Admin button.
From the top menu, click on the Application button and then click Add Application.
Select application type Web and click Next.
Give the app a name. I named mine “Quarkus Demo”.
Under Login redirect URIs, add a new URI: https://oidcdebugger.com/debug
.
Under Grant types allowed, check Implicit (Hybrid).
The rest of the default values will work.
Click Done.
Leave the page open or take note of the Client ID. You’ll need it in a bit when you generate a token.
Update TokenSecuredResource
Now update the TokenSecuredResource
class to do two things:
1) Use CDI dependency injection to inject the groups
claim from the JWT, if it’s available.
2) Add a default endpoint for the /secured
path that is protected by OAuth 2.0 and requires the Everyone
group to access.
Change TokenSecuredResource.java
to the following:
package com.okta.quarkus.jwt;
import java.security.Principal;
import java.util.Set;
import javax.annotation.security.PermitAll;
import javax.annotation.security.RolesAllowed;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.SecurityContext;
import org.eclipse.microprofile.jwt.Claim;
import org.eclipse.microprofile.jwt.JsonWebToken;
/**
* Version 1 of the TokenSecuredResource */
@Path("/secured")
@RequestScoped
public class TokenSecuredResource {
@Inject
JsonWebToken jwt;
@Inject
@Claim("groups")
private Set<String> groups;
@GET()
@Path("permit-all")
@PermitAll
@Produces(MediaType.TEXT_PLAIN)
public String hello(@Context SecurityContext ctx) {
Principal caller = ctx.getUserPrincipal();
String name = caller == null ? "anonymous" : caller.getName();
boolean hasJWT = jwt != null;
String groupsString = groups != null ? groups.toString() : "";
String helloReply = String.format("hello + %s, isSecure: %s, authScheme: %s, hasJWT: %s, groups: %s", name, ctx.isSecure(), ctx.getAuthenticationScheme(), hasJWT, groupsString);
return helloReply;
}
@GET()
@Path("/")
@RolesAllowed({"Everyone"})
@Produces(MediaType.TEXT_PLAIN)
public String helloRolesAllowed(@Context SecurityContext ctx) {
Principal caller = ctx.getUserPrincipal();
String name = caller == null ? "anonymous" : caller.getName();
boolean hasJWT = jwt != null;
String groupsString = groups != null ? groups.toString() : "";
String helloReply = String.format("hello + %s, isSecure: %s, authScheme: %s, hasJWT: %s, groups: %s\"", name, ctx.isSecure(), ctx.getAuthenticationScheme(), hasJWT, groupsString);
return helloReply;
}
}
Try out the new default endpoint:
$ http :8080/secured
HTTP/1.1 401 Unauthorized
...
Not authorized
Generate an OAuth 2.0 Access Token to Test Authentication in Quarkus
Open the OpenID Connect Debugger. You’re going to use this page to generate a JWT access token that you can use to authenticate.
Follow the below steps to continue:
- Set the Authorize URI to:
https://{yourOktaDomain}/oauth2/default/v1/authorize
- Copy your Client ID from the Okta OIDC application you created above and fill it in under Client ID
- Change the scope to be
openid email profile
- Add something for State. It doesn’t matter what. Just can’t be blank
- Scroll down. Click Send
- Copy the resulting JWT Access Token to the clipboard, and in the terminal where you are running your HTTPie commands, save the token value to a shell variable, like so:
TOKEN=eyJraWQiOiJxMm5rZmtwUDRhMlJLV2REU2JfQ...
Test the JWT With the Protected Quarkus Endpoint
Now that you have a valid JWT from your OAuth provider (Okta), you should be able to use this JWT to authenticate against the protected endpoint.
Try it out:
http :8080/secured "Authorization: Bearer $TOKEN"
You should see something like:
HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 123
Content-Type: text/plain;charset=UTF-8
hello + andrew@gmail.com, isSecure: false, authScheme: MP-JWT, hasJWT: true, groups: [Everyone, Admin]"
Add Functionality to Quarkus REST Endpoint
The first step to building a more realistic REST resource is to create a data model class. To do this, you can use a helper project called Lombok (more on their website).
To work with Lombok, add the following dependency to your pom.xml
.
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
<scope>provided</scope>
</dependency>
NOTE: If you’re using an IDE to build your project, and not the command line, install the Lombok plugin for it. For example, see the Lombok IntelliJ plugin.
You’re only going to use a small part of Lombok, the @Data
annotation, to save time writing boilerplate code in the data model class (check out the annotation docs, if you like). It generates getters, setters, equals()
, and hashCode()
methods.
Go ahead and create a Java class: src/main/java/com/okta/quarkus/jwt/Kayak.java
.
package com.okta.quarkus.jwt;
import lombok.Data;
import java.util.Objects;
@Data
public class Kayak {
private String make;
private String model;
private Integer length;
public Kayak() {
}
public Kayak(String make, String model, Integer length) {
this.make = make;
this.model = model;
this.length = length;
}
}
You may have guessed by now that your new REST endpoint is going to manage a list of kayaks.
This code is pure JAX-RS and is not specific to working with Quarkus or Kubernetes. JAX-RS is the Java API for Restful Web Services, an annotation-based specification for configuring REST services. Because it’s just a spec, the actual implementation is provided by the Quarkus stack.
Now, you need to create the REST endpoint resource: src/main/java/com/okta/quarkus/jwt/KayakResource.java
.
package com.okta.quarkus.jwt;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Set;
import javax.annotation.security.RolesAllowed;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
@Path("/kayaks")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class KayakResource {
private Set<Kayak> kayaks = Collections.newSetFromMap(Collections.synchronizedMap(new LinkedHashMap<>()));
public KayakResource() {
kayaks.add(new Kayak("NDK", "Romany", 17));
kayaks.add(new Kayak("NDK", "Surf", 16));
kayaks.add(new Kayak("P&H", "Scorpio HV", 15));
}
@GET
public Set<Kayak> list() {
return kayaks;
}
@RolesAllowed({"Everyone"})
@POST
public Set<Kayak> add(Kayak kayak) {
kayaks.add(kayak);
return kayaks;
}
@RolesAllowed({"Everyone"})
@DELETE
public Set<Kayak> delete(Kayak kayak) {
kayaks.remove(kayak);
return kayaks;
}
}
I won’t go into a ton of detail here, but I do want to point out a few things. First, notice the @Produces
and @Consumes
annotations. The Quarkus docs state that it is very important to include these as they’re used to optimize the final build. This endpoint uses JSON, specified by the constant MediaType.APPLICATION_JSON
.
Also, notice that the GET endpoint is public, but the POST and DELETE endpoints require membership in the Everyone
group. Don’t confuse Everyone
with anonymous. Everyone
is a catch-all, default group assigned to anyone that authenticates on the Okta OIDC application. In this instance, it means that a user has authenticated but isn’t necessarily part of any other group, such as Admin
.
Test Your Quarkus Endpoint and Add a New Kayak
Since you added a new dependency to your pom.xml
, you’ll need to restart the Maven process running your server.
Then, test the POST endpoint without a token to verify that it’s protected.
$ http POST :8080/kayaks make="P&H" model="Cetus HV" length=18
HTTP/1.1 401 Unauthorized
...
Not authorized
Now, try again with the token. You’ll see that a new kayak has been added to the list!
$ http POST :8080/kayaks make="P&H" model="Cetus HV" length=18 "Authorization: Bearer $TOKEN"
HTTP/1.1 200 OK
Connection: keep-alive
...
[
{
"length": 17,
"make": "NDK",
"model": "Romany"
},
{
"length": 16,
"make": "NDK",
"model": "Surf"
},
{
"length": 15,
"make": "P&H",
"model": "Scorpio HV"
},
{
"length": 18,
"make": "P&H",
"model": "Cetus HV"
}
]
You can delete the newly added kayak with the following command:
http DELETE :8080/kayaks make="P&H" model="Cetus HV" length=18 "Authorization: Bearer $TOKEN"
You might notice there’s no PUT (update) in this service. In a more complete service, each record would have a unique ID of some type associated with it. This would allow a client app to specify a specific record for update and deletion (instead of using the record properties themselves and the equals()
method).
Also, clearly, this resource is pretty naive, storing the data in a class property. In a real application, JPA annotations could be used to map the data model to a database for easy serialization and deserialization.
Learn More About Java, Quarkus, and Token Authentication
All done! In this tutorial, you used Quarkus and Java to create a simple REST service, secured with JWT OAuth using Okta as an OAuth/OIDC provider. You also saw how to use CDI dependency injection to inspect JWT claims and retrieve information about the authenticated (or not) client. Finally, you tried some of the basics of RBAC (role-based authentication).
As a reminder, this tutorial was inspired by the Quarkus post: Using JWT RBAC.
Quarkus has a ton of other great guides on their website.
You can find the source code for this tutorial at oktadeveloper/okta-quarkus-example.
Here are some related blog posts to learn more about Java and authentication:
- Simple Token Authentication for Java Apps
- Build a Web App with Spring Boot and Spring Security in 15 Minutes
- Create a Secure Spring REST API
- Build a Simple CRUD App with Spring Boot and Vue.js
If you have any questions about this post, please add a comment below. For more awesome content, follow @oktadev on Twitter, like us on Facebook, or subscribe to our YouTube channel.
How to Develop a Quarkus App with Java and OIDC Authentication was originally published on the Okta Developer Blog on September 30, 2019.
Published at DZone with permission of Andrew Hughes, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments