WildFly 19 includes support for Microprofile JWT Api. In this tutorial we will see how to set up and deploy a REST Application which uses Microprofile JWT for Role Based Access Control. The application will run on the top of Wildly 19 and uses Keycloak as Identity and Access management service.


Today, the most common solutions for handling security of RESTful microservices are by means of solid standard which are based on OAuth2, OpenID Connect and JSON Web Token (JWT). In a nutshell JWT (JSON Web Token) is a compact, URL-safe means of representing claims to be transferred between two identities. The claims in a JWT are encoded as a JSON object that is used as the payload of a JSON Web Signature (JWS) structure or as the plaintext of a JSON Web Encryption (JWE) structure, enabling the claims to be digitally signed or integrity protected with a Message Authentication Code (MAC) and/or encrypted.

Triggering the "jwt" subsystem on WildFly

In WildFly, the JWT API is provided by means of this extension:

<extension module="org.wildfly.extension.microprofile.jwt-smallrye"/>

With the current release of WildFly, there are no specific properties you can set into the microprofile-jwt-smallrye. However for the purpose of using its API, we can set some specific attributes of JWT in the Microprofile configuration file (microprofile-config.properties). But let's go with order. The first thing we need to do is marking a JAX-RS Application as requiring JWT RBAC.
This can be done through the annotation @LoginConfig to your existing @javax.ws.rs.core.Application subclass. So, here is our first element we will include in our application:

package com.mastertheboss.jwt;

import javax.annotation.security.DeclareRoles;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
import org.eclipse.microprofile.auth.LoginConfig;

@ApplicationPath("/rest")
@LoginConfig(authMethod = "MP-JWT", realmName = "myrealm")
@DeclareRoles({"admin","user"})
public class JaxRsActivator extends Application {
    
}

Next, we will add a secure REST Endpoint, which contains just one method which requires the Role of "admin" or "user":

package com.mastertheboss.jwt;

import org.eclipse.microprofile.jwt.Claim;
import javax.annotation.security.RolesAllowed;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;

import javax.ws.rs.*;
import org.eclipse.microprofile.jwt.ClaimValue;
import java.util.Set;

@Path("secure")
@ApplicationScoped
@Produces("application/json")
@Consumes("application/json")

public class SecureEndpoint {

    @Inject
    @Claim("groups")
    private Set<String> groups;

    @Inject
    @Claim("sub")
    private String subject;

    @GET
    @RolesAllowed({"admin","user"})
    public String echoRoleAndSubject() {
        return "You are logged with " +this.subject + ": " + this.groups.toString();

    }
}

In order to return the username and the groups whom the user belongs to, we use the @Claim annotation which captures a set of claims. The JWT specification lists several “registered claims” to achieve specific goals. All of them, however, are optional. The mandatory ones are:

  • exp: This will be used by the MicroProfile JWT implementation to ensure old tokens are rejected.
  • sub: This will be the value returned from any getCallerPrinciple() calls made by the application.
  • groups: This will be the value used in any isCallerInRole() calls made by the application and any @RolesAllowed checks.

JWT settings required in your application

The next thing we need to do is configuring the public key to verify the signature of the JWT that is attached in the Authorization header. This is typically configured within the src/main/resources/META-INF/microprofile-config.properties this way:

mp.jwt.verify.publickey.location=/META-INF/keycloak-public-key.pem

Since MP JWT 1.1, however the key may be provided as a string in the mp.jwt.verify.publickey config property or as a file location or URL.

If you are using Keycloak version 6.0.0, or newer, this is even simpler as there is built-in Client Scope which makes it really easy to issue tokens and you don't have to add the public key to the PEM file. Instead you point to Keycloak JWK's endpoint.
So, once you have defined your Keycloak realm with a Client configuration bound to it, you can just add a reference to it into the src/main/resources/META-INF/microprofile-config.properties file

Here is how to do it in practice:

mp.jwt.verify.publickey.location=http://localhost:8180/auth/realms/myrealm/protocol/openid-connect/certs
mp.jwt.verify.issuer=http://localhost:8180/auth/realms/myrealm

In the above configuration, we have assumed that you are running a realm named "myrealm" and that Keycloak is running on localhost:8180. In the second part of this tutorial we will learn how to configure Keycloak so that it can work as authentication service for our JWT application.

Now complete the configuration adding in your web.xml:

<web-app version="3.1"
         xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd">

    <context-param>
        <param-name>resteasy.role.based.security</param-name>
        <param-value>true</param-value>
     </context-param>

     <security-role>
         <role-name>admin</role-name>
         <role-name>user</role-name>
     </security-role>
</web-app>

So we have enavled Role Based Security in our application and declared the available Roles.

Compiling the project

In order to be able to compile applications using JWT, you need to include the following dependency in your application:

<dependency>
  <groupId>org.eclipse.microprofile.jwt</groupId>
  <artifactId>microprofile-jwt-auth-api</artifactId>
  <version>1.1</version>
</dependency>

In addition, your application also needs to include jaxrs, cdi and jboss-annotation API in order to build the above example:

<dependency>
  <groupId>jakarta.enterprise</groupId>
  <artifactId>jakarta.enterprise.cdi-api</artifactId>
  <scope>provided</scope>
</dependency>
<dependency>
  <groupId>org.jboss.spec.javax.ws.rs</groupId>
  <artifactId>jboss-jaxrs-api_2.1_spec</artifactId>
  <scope>provided</scope>
</dependency>
<dependency>
  <groupId>org.jboss.spec.javax.annotation</groupId>
  <artifactId>jboss-annotations-api_1.3_spec</artifactId>
  <scope>provided</scope>
</dependency>

Finally, we will import the jakartaee8-with-tools BOM to add version for our dependencies:

<dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.wildfly.bom</groupId>
        <artifactId>wildfly-jakartaee8-with-tools</artifactId>
        <version>${version.server.bom}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
</dependencyManagement>

In the next article, we will show how to configure Keycloak Realm so that we can test our application by requesting a Token to Keycloak and use that to invoke the secured method. Stay tuned!

0
0
0
s2sdefault