Skip to content

Latest commit

 

History

History
1640 lines (1339 loc) · 62.5 KB

README.adoc

File metadata and controls

1640 lines (1339 loc) · 62.5 KB

micro-profile-jwt: MicroProfile JWT QuickStart

The microprofile-jwt quickstart demonstrates the use of the MicroProfile JWT specification in WildFly.

What is it?

The microprofile-jwt quickstart demonstrates the use of the MicroProfile JWT specification in WildFly. Within the quickstart the following topics will be covered: -

  • Creation of a simple Rest deployment as the base of the quickstart.

  • Initial calls to verify the endpoint is accessible.

  • Creation of public and private keys to enable generation and validation of JWT tokens.

  • Creation of a simple utility to generate JWT tokens.

  • Activation of MP-JWT authentication for the deployment.

  • Calling the rest endpoint with the generated JWT token.

  • Enabling authorization based on claims contained within the JWT token.

  • Injection of claims from the JWT token.

Considerations for Use in a Production Environment

To make it possible to execute this quickstart out of the box the public and private key are included in the quickstart, as both keys are published publicly they should never be used in a production environment.

Although this quickstart demonstrates the functionality using a token generation utility, within a production environment it would be more appropriate to make use of an identity provider to issue the tokens.

System Requirements

The application this project produces is designed to be run on WildFly Application Server 32 or later.

All you need to build this project is Java 11.0 (Java SDK 11) or later and Maven 3.6.0 or later. See Configure Maven to Build and Deploy the Quickstarts to make sure you are configured correctly for testing the quickstarts.

Start the WildFly Standalone Server

  1. Open a terminal and navigate to the root of the WildFly directory.

  2. Start the WildFly server with the MicroProfile profile by typing the following command.

    $ WILDFLY_HOME/bin/standalone.sh -c standalone-microprofile.xml
    Note
    For Windows, use the WILDFLY_HOME\bin\standalone.bat script.

Configure the Server

One of the benefits of using MicroProfile JWT is that the configuration is contained entirely within the deployment, for this reason no manual configuration of the server is required to execute the quickstart.

Solution

We recommend that you follow the instructions in the next sections and create the application step by step as this will provide a better foundation for creating your own MP-JWT secured deployment, however if you want to jump straight to executing the quickstarts the steps here can be followed to execute the included code.

The quickstart can be deployed to the running application server using the following command: -

mvn package wildfly:deploy

An unauthenticated call can be made to one of the deployed endpoints using the following command: -

$ curl http://localhost:8080/microprofile-jwt/Sample/helloworld
Hello anonymous

Using the token generation utility contained within the quickstart a new JWT can be created using the following command: -

$ mvn exec:java -Dexec.mainClass=org.wildfly.quickstarts.mpjwt.TokenUtil -Dexec.classpathScope=test -Dexec.args="testUser 2017-09-15 Echoer Subscriber"

JWT Header - {"kid":"Test Key","typ":"jwt","alg":"RS256"}

JWT Claims - {"sub":"testUser","upn":"testUser","iss":"quickstart-jwt-issuer","aud":"jwt-audience","groups":["Echoer","Subscriber"],"birthdate":"2017-09-15","jti":"3b89e56f-b8fd-4d5f-a1ed-080b958873f9","iat":1579886816,"exp":1579901216}

Generated JWT Token
eyJraWQiOiJUZXN0IEtleSIsInR5cCI6Imp3dCIsImFsZyI6IlJTMjU2In0.eyJzdWIiOiJ0ZXN0VXNlciIsInVwbiI6InRlc3RVc2VyIiwiaXNzIjoicXVpY2tzdGFydC1qd3QtaXNzdWVyIiwiYXVkIjoiand0LWF1ZGllbmNlIiwiZ3JvdXBzIjpbIkVjaG9lciIsIlN1YnNjcmliZXIiXSwiYmlydGhkYXRlIjoiMjAxNy0wOS0xNSIsImp0aSI6IjNiODllNTZmLWI4ZmQtNGQ1Zi1hMWVkLTA4MGI5NTg4NzNmOSIsImlhdCI6MTU3OTg4NjgxNiwiZXhwIjoxNTc5OTAxMjE2fQ.srUrA3YwWg4V42Kdf5aLQzLiBO9u7ZCJ_bbNBrgU1XFhRBVzpLq4TJheN4ZFvTKDKFHFm2pa1_a_J70EONK9y8mzKnwiIfV1LbugophBE1xp7iDsBeOwGT8IKQVLaoniGGhqny_MkiMmcWt4tRzrTlJ4TOlCGKv3mZ_RzOj6xdWb12RK7-MTZYlRdnPD8kAfRT7QaJnRbuy5vIpqdz8cVg1y2KILBtg_yJB8s2jrx0ZYgTLDUH6yLpcxk43cLszDCfmdaZAoZFjAS9xUdAuk5eo7TBqt376q0BbE5DS8bHlo3ejWcNEwyo1dPD_TlKDJtNgeae89gru5Z2NOmc2XPA

A call can now be made to the first endpoint using the generated JWT token within the request message: -

$ curl -H "Authorization: Bearer eyJ...XPA" http://localhost:8080/microprofile-jwt/Sample/helloworld
Hello testUser

The quickstart also contains an endpoint which requires the identity to be granted the Subscriber role, using a generated token this can be called using the following command: -

$ curl -H "Authorization: Bearer ey..XPA" http://localhost:8080/microprofile-jwt/Sample/subscription
hello + testUser, hasJWT: true

If a token is now generated and use without the Subscriber group access to this endpoint should be rejected.

$ mvn exec:java -Dexec.mainClass=org.wildfly.quickstarts.mpjwt.TokenUtil -Dexec.classpathScope=test -Dexec.args="testUser 2017-09-15 Echoer"

JWT Header - {"kid":"Test Key","typ":"jwt","alg":"RS256"}

JWT Claims - {"sub":"testUser","upn":"testUser","iss":"quickstart-jwt-issuer","aud":"jwt-audience","groups":["Echoer"],"birthdate":"2017-09-15","jti":"4346874f-eb48-4d84-ab51-c6efd1cd22d5","iat":1580136970,"exp":1580151370}

Generated JWT Token
eyJraWQiOiJUZXN0IEtleSIsInR5cCI6Imp3dCIsImFsZyI6IlJTMjU2In0.eyJzdWIiOiJ0ZXN0VXNlciIsInVwbiI6InRlc3RVc2VyIiwiaXNzIjoicXVpY2tzdGFydC1qd3QtaXNzdWVyIiwiYXVkIjoiand0LWF1ZGllbmNlIiwiZ3JvdXBzIjpbIkVjaG9lciJdLCJiaXJ0aGRhdGUiOiIyMDE3LTA5LTE1IiwianRpIjoiNDM0Njg3NGYtZWI0OC00ZDg0LWFiNTEtYzZlZmQxY2QyMmQ1IiwiaWF0IjoxNTgwMTM2OTcwLCJleHAiOjE1ODAxNTEzNzB9.NjWlGGi5iB2PW-g_fs74EgwiyOrgRSsHDym5PS7H3o6T3aEb3ZnMmx7W9uhT_pXvpAL_fboXxy7WMQHukGb_ywtxAmTlA7ecrsia-O9EYV2Gr1ZhlQ9kYVERwZdo5lFqphMvERJ1tClTR3Jk37tpu54HVgFquhUEXlEcldOSeRbYHF9fkiVD6_UPR9bfphhCdwzIbeDVnBJ3FuRl_ZuSxWCoWaRSLPGjuR3JzMlUWkRV8RXhRLTkFRgNowOx_uFFK6q7_aOVqQaHixRqT4ULcO9WiA7f6Up0NMscvq-aT3VFw64qEmn2S8pfX7PpSuAB71Z7w0dwhf4qx3iYklfAPw

$ curl -H "Authorization: Bearer ey..APw" http://localhost:8080/microprofile-jwt/Sample/subscription
Access forbidden: role not allowed

The quickstart also contains an endpoint that makes use of the birthdate claim to calculate how long until the callers birthday, this can be called using a token which contains the Subscriber group.

$ curl -H "Authorization: Bearer ey..XPA" http://localhost:8080/microprofile-jwt/Sample/birthday
7 months and 19 days until your next birthday.

This section has been a fast run through of the commands required to deploy the quickstart, generate JWT tokens and call the various endpoints, for further information as to how this was achieved please read through the next section where the process to generate this quickstart from scratch is described step by step.

It is also possible to run each of these calls from the test case included in the quickstart.

Run the Integration Tests

This quickstart includes integration tests, which are located under the src/test/ directory. The integration tests verify that the quickstart runs correctly when deployed on the server.

Follow these steps to run the integration tests.

  1. Make sure WildFly server is started.

  2. Make sure the quickstart is deployed.

  3. Type the following command to run the verify goal with the integration-testing profile activated.

    $ mvn verify -Pintegration-testing 

Starting from Scratch

In this section we will go through the steps to create a new JAX-RS deployment from scratch and incrementally add support for MicroProfile JWT including token generation, activation of authorization and the injection of claims.

Project Generation

This quickstart will be making use of JAX-RS endpoints secured using MicroProfile JWT so the first step is to generate a new webapp project: -

mvn archetype:generate -DinteractiveMode=false \
    -DarchetypeGroupId=org.apache.maven.archetypes \
    -DarchetypeArtifactId=maven-archetype-webapp \
    -DgroupId=org.wildfly.quickstarts -DartifactId=microprofile-jwt \
    -Dversion=1.0.0.Alpha1-SNAPSHOT
cd microprofile-jwt

The changes required on the project can be performed using a text editor, however you may prefer to import the project into your favourite IDE.

pom.xml Updates

Next the project’s pom.xml should be updated so the dependencies required by this quickstart are available and so we have a plug-in installed which can deploy the quickstart directly to WildFly.

Add the following properties to the pom.xml.

<version.com.nimbusds.jose.jwt>{versionComNimbusdsJoseJwt}</version.com.nimbusds.jose.jwt>
<version.glassfish.json>{versionGlassfishJson}</version.glassfish.json>
<version.bom.microprofile>{versionMicroprofileBom}</version.bom.microprofile>
<version.bom.ee>{versionServerBom}</version.bom.ee>

Also the project can be updated to use Java 11 as the minimum.

<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>

Before the dependencies are defined add the following boms.

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

By using boms the majority of dependencies used within this quickstart align with the version uses by the application server.

The following dependencies can now be added to the project.

<dependency>
  <groupId>jakarta.annotation</groupId>
  <artifactId>jakarta.annotation-api</artifactId>
  <scope>provided</scope>
</dependency>
<dependency>
  <groupId>jakarta.enterprise</groupId>
  <artifactId>jakarta.enterprise.cdi-api</artifactId>
  <scope>provided</scope>
</dependency>
<dependency>
  <groupId>jakarta.ws.rs</groupId>
  <artifactId>jakarta.ws.rs-api</artifactId>
  <scope>provided</scope>
</dependency>
<dependency>
  <groupId>org.eclipse.microprofile.jwt</groupId>
  <artifactId>microprofile-jwt-auth-api</artifactId>
  <scope>provided</scope>
</dependency>

Also add the following test scoped dependencies.

<dependency>
  <groupId>com.nimbusds</groupId>
  <artifactId>nimbus-jose-jwt</artifactId>
  <version>${version.com.nimbusds.jose.jwt}</version>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>jakarta.json</groupId>
  <artifactId>jakarta.json-api</artifactId>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>org.glassfish</groupId>
  <artifactId>jakarta.json</artifactId>
  <version>${version.glassfish.json}</version>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>org.eclipse.parsson</groupId>
  <artifactId>parsson</artifactId>
  <scope>test</scope>
</dependency>

Within the <build></build> section of the project add the following plug-in definition.

<plugins>
  <plugin>
    <groupId>org.wildfly.plugins</groupId>
    <artifactId>wildfly-maven-plugin</artifactId>
  </plugin>
</plugins>

Once the project has been developed this plug-in will handle the deployment to the application server.

Setup the required Maven repositories (if you don’t have them set up in Maven global settings):

<repositories>
    <repository>
        <id>jboss-public-maven-repository</id>
        <name>JBoss Public Maven Repository</name>
        <url>https://repository.jboss.org/nexus/content/groups/public</url>
        <layout>default</layout>
        <releases>
            <enabled>true</enabled>
            <updatePolicy>never</updatePolicy>
        </releases>
        <snapshots>
            <enabled>true</enabled>
            <updatePolicy>never</updatePolicy>
        </snapshots>
    </repository>
    <repository>
        <id>redhat-ga-maven-repository</id>
        <name>Red Hat GA Maven Repository</name>
        <url>https://maven.repository.redhat.com/ga/</url>
        <layout>default</layout>
        <releases>
            <enabled>true</enabled>
            <updatePolicy>never</updatePolicy>
        </releases>
        <snapshots>
            <enabled>true</enabled>
            <updatePolicy>never</updatePolicy>
        </snapshots>
    </repository>
</repositories>
<pluginRepositories>
    <pluginRepository>
        <id>jboss-public-maven-repository</id>
        <name>JBoss Public Maven Repository</name>
        <url>https://repository.jboss.org/nexus/content/groups/public</url>
        <releases>
            <enabled>true</enabled>
        </releases>
        <snapshots>
            <enabled>true</enabled>
        </snapshots>
    </pluginRepository>
    <pluginRepository>
        <id>redhat-ga-maven-repository</id>
        <name>Red Hat GA Maven Repository</name>
        <url>https://maven.repository.redhat.com/ga/</url>
        <releases>
            <enabled>true</enabled>
        </releases>
        <snapshots>
            <enabled>true</enabled>
        </snapshots>
    </pluginRepository>
</pluginRepositories>

JAX-RS Conversion

The generated project is a standard web application so the next step will be to delete the files not required by this quickstart and add the JAX-RS application and a helloWorld endpoint.

Under src/main/webapp delete the generated index.jsp.

Within the directory src/main/webapp/WEB-INF add an empty beans.xml file, this will act as a trigger to enable CDI for the deployment.

Under src/main create a new java directory to hold the Java class to be added to the project.

Add the following class in the org.wildfly.quickstarts.mpjwt package, this will be the main trigger to process the deployment as a JAX-RS deployment.

package org.wildfly.quickstarts.mpjwt;

import jakarta.ws.rs.ApplicationPath;
import jakarta.ws.rs.core.Application;

import org.eclipse.microprofile.auth.LoginConfig;

@ApplicationPath("/")
@LoginConfig(authMethod="MP-JWT", realmName="MP JWT Realm")
public class App extends Application {}

Also within the org.wildfly.quickstarts.mpjwt package add the following endpoint.

package org.wildfly.quickstarts.mpjwt;

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;


@Path("/Sample")
public class SampleEndPoint {

    @GET
    @Path("/helloworld")
    public String helloworld() {
        return "Hello World";
    }

}

At this stage the project contains a simple JAX-RS endpoint we can deploy to the server and invoke to verify everything is working as expected before moving on to enable JWT based authentication and authorization.

First build the example and deploy it to the previously started application server.

  1. Compile the application code and deploy it to WildFly

$ mvn clean package wildfly:deploy

In the output of the application server’s console check the deployment deployed successfully.

15:48:22,159 INFO  [org.jboss.resteasy.resteasy_jaxrs.i18n] (ServerService Thread Pool -- 78) RESTEASY002225: Deploying jakarta.ws.rs.core.Application: class org.wildfly.quickstarts.mpjwt.App$Proxy$_$$_WeldClientProxy
15:48:22,193 INFO  [org.wildfly.extension.undertow] (ServerService Thread Pool -- 78) WFLYUT0021: Registered web context: '/microprofile-jwt' for server 'default-server'
15:48:22,239 INFO  [org.jboss.as.server] (management-handler-thread - 1) WFLYSRV0010: Deployed "microprofile-jwt.war" (runtime-name : "microprofile-jwt.war")

The endpoint can now be called using the following command: -

$ curl http://localhost:8080/microprofile-jwt/Sample/helloworld
Hello World

Alternative HTTP clients can be used however later on we will be adding custom headers to the HTTP request which can be acomplished directly with the curl command.

Report Authenticated Identity

The first update to make to the project is to modify the /helloworld to report the name of the presently authenticated identity.

Make the following changes to the previously created org.wildfly.quickstarts.mpjwt.SampleEndPoint class.

Add the following import statements to the class.

import java.security.Principal;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.SecurityContext;

Then replace the existing helloWorld() method with the following: -

@GET
@Path("/helloworld")
public String helloworld(@Context SecurityContext securityContext) {
    Principal principal = securityContext.getUserPrincipal();
    String caller = principal == null ? "anonymous" : principal.getName();

    return "Hello " + caller;
}

As before, deploy and call the endpoint.

$ mvn package wildfly:deploy

$ curl http://localhost:8080/microprofile-jwt/Sample/helloworld
Hello anonymous

Public / Private Key

Now that we have a deployable project our next step on the project will be to enable MP-JWT authentication, however before we can do this a couple of steps are required the first being the generation of a public / private key pair for token signing and verification.

For MicroProfile JWT we only require the keys, we do not require an X509Certificate so we can use openssl to generate the raw keys.

$ openssl genpkey -algorithm RSA -out private.pem -pkeyopt rsa_keygen_bits:2048

From this we can then export the public key that will be included in the deployment.

$ mkdir -p src/main/resources/META-INF
$ openssl rsa -in private.pem -pubout -out src/main/resources/META-INF/public.pem

The commands used here offer no protection of the generated keys, in a real world environment any private keys that can be used for token generation should be handled securely.

Token Generator

The next step is to add a utility to the project which can be used to generate JWT tokens for use with this example.

First create a directory to hold the test code.

$ mkdir -p src/test/java

Under src/test/java add the following utility class to the org.wildfly.quickstarts.mpjwt package.

package org.wildfly.quickstarts.mpjwt;

import java.io.FileInputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
import java.util.UUID;

import jakarta.json.Json;
import jakarta.json.JsonArrayBuilder;
import jakarta.json.JsonObjectBuilder;

import com.nimbusds.jose.JOSEObjectType;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jose.JWSObject;
import com.nimbusds.jose.JWSSigner;
import com.nimbusds.jose.Payload;
import com.nimbusds.jose.crypto.RSASSASigner;

public class TokenUtil {

    private static PrivateKey loadPrivateKey(final String fileName) throws Exception {
        try (InputStream is = new FileInputStream(fileName)) {
            byte[] contents = new byte[4096];
            int length = is.read(contents);
            String rawKey = new String(contents, 0, length, StandardCharsets.UTF_8)
                    .replaceAll("-----BEGIN (.*)-----", "")
                    .replaceAll("-----END (.*)----", "")
                    .replaceAll("\r\n", "").replaceAll("\n", "").trim();

            PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(rawKey));
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");

            return keyFactory.generatePrivate(keySpec);
        }
    }

    public static String generateJWT(final String principal, final String birthdate, final String...groups) throws Exception {
    	PrivateKey privateKey = loadPrivateKey("private.pem");

        JWSSigner signer = new RSASSASigner(privateKey);
        JsonArrayBuilder groupsBuilder = Json.createArrayBuilder();
        for (String group : groups) { groupsBuilder.add(group); }

        long currentTime = System.currentTimeMillis() / 1000;
        JsonObjectBuilder claimsBuilder = Json.createObjectBuilder()
                .add("sub", principal)
                .add("upn", principal)
                .add("iss", "quickstart-jwt-issuer")
                .add("aud", "jwt-audience")
                .add("groups", groupsBuilder.build())
                .add("birthdate", birthdate)
                .add("jti", UUID.randomUUID().toString())
                .add("iat", currentTime)
                .add("exp", currentTime + 14400);

        JWSObject jwsObject = new JWSObject(new JWSHeader.Builder(JWSAlgorithm.RS256)
                .type(new JOSEObjectType("jwt"))
                .keyID("Test Key").build(),
                new Payload(claimsBuilder.build().toString()));

        jwsObject.sign(signer);

        return jwsObject.serialize();
    }

    public static void main(String[] args) throws Exception {
        if (args.length < 2) throw new IllegalArgumentException("Usage TokenUtil {principal} {birthdate} {groups}");
        String principal = args[0];
        String birthdate = args[1];
        String[] groups = new String[args.length - 2];
        System.arraycopy(args, 2, groups, 0, groups.length);

        String token = generateJWT(principal, birthdate, groups);
        String[] parts = token.split("\\.");
        System.out.println(String.format("\nJWT Header - %s", new String(Base64.getDecoder().decode(parts[0]), StandardCharsets.UTF_8)));
        System.out.println(String.format("\nJWT Claims - %s", new String(Base64.getDecoder().decode(parts[1]), StandardCharsets.UTF_8)));
        System.out.println(String.format("\nGenerated JWT Token \n%s\n", token));
    }
}

After adding the class the project will need to be compiled again and can then be executed using Maven.

$ mvn package
...
$ mvn exec:java -Dexec.mainClass=org.wildfly.quickstarts.mpjwt.TokenUtil -Dexec.classpathScope=test -Dexec.args="testUser 2017-09-15 Echoer Subscriber"

JWT Header - {"kid":"Test Key","typ":"jwt","alg":"RS256"}

JWT Claims - {"sub":"testUser","upn":"testUser","iss":"quickstart-jwt-issuer","aud":"jwt-audience","groups":["Echoer","Subscriber"],"birthdate":"2017-09-15","jti":"3b89e56f-b8fd-4d5f-a1ed-080b958873f9","iat":1579886816,"exp":1579901216}

Generated JWT Token
eyJraWQiOiJUZXN0IEtleSIsInR5cCI6Imp3dCIsImFsZyI6IlJTMjU2In0.eyJzdWIiOiJ0ZXN0VXNlciIsInVwbiI6InRlc3RVc2VyIiwiaXNzIjoicXVpY2tzdGFydC1qd3QtaXNzdWVyIiwiYXVkIjoiand0LWF1ZGllbmNlIiwiZ3JvdXBzIjpbIkVjaG9lciIsIlN1YnNjcmliZXIiXSwiYmlydGhkYXRlIjoiMjAxNy0wOS0xNSIsImp0aSI6IjNiODllNTZmLWI4ZmQtNGQ1Zi1hMWVkLTA4MGI5NTg4NzNmOSIsImlhdCI6MTU3OTg4NjgxNiwiZXhwIjoxNTc5OTAxMjE2fQ.srUrA3YwWg4V42Kdf5aLQzLiBO9u7ZCJ_bbNBrgU1XFhRBVzpLq4TJheN4ZFvTKDKFHFm2pa1_a_J70EONK9y8mzKnwiIfV1LbugophBE1xp7iDsBeOwGT8IKQVLaoniGGhqny_MkiMmcWt4tRzrTlJ4TOlCGKv3mZ_RzOj6xdWb12RK7-MTZYlRdnPD8kAfRT7QaJnRbuy5vIpqdz8cVg1y2KILBtg_yJB8s2jrx0ZYgTLDUH6yLpcxk43cLszDCfmdaZAoZFjAS9xUdAuk5eo7TBqt376q0BbE5DS8bHlo3ejWcNEwyo1dPD_TlKDJtNgeae89gru5Z2NOmc2XPA

We now have everything we need to activate MP-JWT authentication on the deployment and start accepting JWT tokens within the HTTP requests.

Activating MicroProfile JWT

At this stage we have a basic JAX-RS deployment, a pair of keys for the generation of and validation of JWT tokens and a utility to generate JWT tokens. Our next step is to activate MicroProfile JWT authentication for the deployment and we can then make use of some of the advanced features such as role based access control using the groups from within the token and access to individual claims within the token.

The configuration for JWT validation can be contained within a MicroProfile Config properties file, under src/main/resources/META-INF add a microprofile-config.properties with the following content.

mp.jwt.verify.publickey.location=META-INF/public.pem
mp.jwt.verify.issuer=quickstart-jwt-issuer

As before, deploy and call the endpoint.

$ mvn package wildfly:deploy

$ curl http://localhost:8080/microprofile-jwt/Sample/helloworld
Hello anonymous

A new token can now be generated using the TokenUtility created previously and the token passed in as we call the endpoint.

$ curl -H "Authorization: Bearer eyJ...59g" http://localhost:8080/microprofile-jwt/Sample/helloworld
Hello testUser

At this point our first endpoint now supports optional authentication making use of a JWT token.

Authorization

Now that authentication has been activated we can add another endpoint that requires the caller to have been granted the ‘Subscribers’ role which will be based on the groups in the incoming JWT token.

The following imports should be added to the “SampleEndPoint” class: -

import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;

import org.eclipse.microprofile.jwt.JsonWebToken;

And then the following new endpoint can be added: -

@Inject
JsonWebToken jwt;

@GET()
@Path("/subscription")
@RolesAllowed({"Subscriber"})
public String helloRolesAllowed(@Context SecurityContext ctx) {
    Principal caller =  ctx.getUserPrincipal();
    String name = caller == null ? "anonymous" : caller.getName();
    boolean hasJWT = jwt.getClaimNames() != null;
    String helloReply = String.format("hello + %s, hasJWT: %s", name, hasJWT);
    return helloReply;
}

Within WildFly the JAX-RS deployment is handled by RESTEasy so under src/main/webapp/WEB-INF add the following to the web.xml to enable RESTEasy authorization checks.

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

<security-role>
    <role-name>Subscriber</role-name>
</security-role>

As before, build and deploy the example.

$ mvn package wildfly:deploy

Using a recently generated token the new endpoint can be called.

$ curl -H "Authorization: Bearer ey..59g" http://localhost:8080/microprofile-jwt/Sample/subscription
hello + testUser, hasJWT: true

If a token is now generated without including the “Subscriber” group access should be denied to the endpoint.

$ curl -H "Authorization: Bearer ey..Pw" http://localhost:8080/microprofile-jwt/Sample/subscription
Access forbidden: role not allowed

Claims

A final feature that will be explored within this Quickstart is accessing the claims contained within the JWT.

The JWTToken in the previous example can be used to inspect the claims, however it is also possible to inject the specific claim.

The following imports should be added to the “SampleEndPoint” class: -

import java.time.LocalDate;
import java.time.Period;
import java.util.Optional;

import org.eclipse.microprofile.jwt.Claims;
import org.eclipse.microprofile.jwt.Claim;

And then the following new endpoint can be added: -

@Inject
@Claim(standard = Claims.birthdate)
Optional<String> birthdate;

@GET()
@Path("/birthday")
@RolesAllowed({ "Subscriber" })
public String birthday() {
    if (birthdate.isPresent()) {
        LocalDate birthdate = LocalDate.parse(this.birthdate.get().toString());
        LocalDate today = LocalDate.now();
        LocalDate next = birthdate.withYear(today.getYear());
        if (today.equals(next)) {
            return "Happy Birthday";
        }
        if (next.isBefore(today)) {
            next = next.withYear(next.getYear() + 1);
        }
        Period wait = today.until(next);
        return String.format("%d months and %d days until your next birthday.", wait.getMonths(), wait.getDays());
    }
    return "Sorry, we don't know your birthdate.";
}

As with the previous endpoint the caller is required to have been granted the Subscriber role, if a birthdate claim is present within the JWT token this endpoint will calculate how many days until the next birthday and wish you happy birthday it if is today.

As before, build and deploy the example.

$ mvn package wildfly:deploy

Using a recently generated token the new endpoint can be called.

$ curl -H "Authorization: Bearer ey..59g" http://localhost:8080/microprofile-jwt/Sample/birthday
7 months and 19 days until your next birthday.

Building and running the quickstart application in a bootable JAR

You can use the WildFly JAR Maven plug-in to build a WildFly bootable JAR to run this quickstart.

The quickstart pom.xml file contains a Maven profile named bootable-jar which configures the bootable JAR building:

      <profile>
          <id>bootable-jar</id>
          <build>
              <plugins>
                  <plugin>
                      <groupId>org.wildfly.plugins</groupId>
                      <artifactId>wildfly-maven-plugin</artifactId>
                      <configuration>
                          <discover-provisioning-info>
                              <version>${version.server}</version>
                          </discover-provisioning-info>
                          <bootable-jar>true</bootable-jar>
                          <!--
                            Rename the output war to ROOT.war before adding it to the server, so that the
                            application is deployed in the root web context.
                          -->
                          <name>ROOT.war</name>
                          <add-ons>...</add-ons>
                      </configuration>
                      <executions>
                          <execution>
                              <goals>
                                  <goal>package</goal>
                              </goals>
                          </execution>
                      </executions>
                  </plugin>
                  ...
              </plugins>
          </build>
      </profile>

The plugin uses WildFly Glow to discover the feature packs and layers required to run the application, and provisions a server containing those layers.

If you get an error or the server is missing some functionality which cannot be auto-discovered, you can download the WildFly Glow CLI and run the following command to see more information about what add-ons are available:

wildfly-glow show-add-ons
Procedure
  1. Build the quickstart bootable JAR with the following command:

    $ mvn clean package -Pbootable-jar
  2. Run the quickstart application contained in the bootable JAR:

    $ java -jar target/microprofile-jwt-bootable.jar
  3. You can now interact with the quickstart application.

Note

After the quickstart application is deployed, the bootable JAR includes the application in the root context. Therefore, any URLs related to the application should not have the /microprofile-jwt path segment after HOST:PORT.

Run the Integration Tests with a bootable jar

The integration tests included with this quickstart, which verify that the quickstart runs correctly, may also be run with a bootable jar.

Follow these steps to run the integration tests.

  1. Make sure the bootable jar is provisioned.

    $ mvn clean package -Pbootable-jar
  2. Start the WildFly bootable jar, this time using the WildFly Maven Jar Plugin, which is recommend for testing due to simpler automation.

    $ mvn wildfly-jar:start -Djar-file-name=target/microprofile-jwt-bootable.jar
  3. Type the following command to run the verify goal with the integration-testing profile activated, and specifying the quickstart’s URL using the server.host system property, which for a bootable jar by default is http://localhost:8080.

    $ mvn verify -Pintegration-testing -Dserver.host=http://localhost:8080
  4. Shutdown the WildFly bootable jar, this time using the WildFly Maven Jar Plugin too.

    $ mvn wildfly-jar:shutdown

Building and running the quickstart application with OpenShift

Build the WildFly Source-to-Image (S2I) Quickstart to OpenShift with Helm Charts

On OpenShift, the S2I build with Apache Maven uses an openshift Maven profile to provision a WildFly server, deploy and run the quickstart in OpenShift environment.

The server provisioning functionality is provided by the WildFly Maven Plugin, and you may find its configuration in the quickstart pom.xml:

        <profile>
            <id>openshift</id>
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.wildfly.plugins</groupId>
                        <artifactId>wildfly-maven-plugin</artifactId>
                        <configuration>
                            <discover-provisioning-info>
                                <version>${version.server}</version>
                                <context>cloud</context>
                            </discover-provisioning-info>
                            <!--
                                The parent POM's 'openshift' profile renames the output archive to ROOT.war so that the
                                application is deployed in the root web context. Add ROOT.war to the server.
                            -->
                            <filename>ROOT.war</filename>
                            <add-ons>...</add-ons>
                        </configuration>
                        <executions>
                            <execution>
                                <goals>
                                    <goal>package</goal>
                                </goals>
                            </execution>
                        </executions>
                    </plugin>
                    ...
                </plugins>
            </build>
        </profile>

You may note that unlike the provisioned-server profile it uses the cloud context which enables a configuration tuned for OpenShift environment.

The plugin uses WildFly Glow to discover the feature packs and layers required to run the application, and provisions a server containing those layers.

If you get an error or the server is missing some functionality which cannot be auto-discovered, you can download the WildFly Glow CLI and run the following command to see more information about what add-ons are available:

wildfly-glow show-add-ons

Getting Started with WildFly for OpenShift and Helm Charts

This section contains the basic instructions to build and deploy this quickstart to WildFly for OpenShift or WildFly for OpenShift Online using Helm Charts.

Prerequisites

  • You must be logged in OpenShift and have an oc client to connect to OpenShift

  • Helm must be installed to deploy the backend on OpenShift.

Once you have installed Helm, you need to add the repository that provides Helm Charts for WildFly.

$ helm repo add wildfly https://docs.wildfly.org/wildfly-charts/
"wildfly" has been added to your repositories
$ helm search repo wildfly
NAME                    CHART VERSION   APP VERSION     DESCRIPTION
wildfly/wildfly         ...             ...            Build and Deploy WildFly applications on OpenShift
wildfly/wildfly-common  ...             ...            A library chart for WildFly-based applications

Deploy the WildFly Source-to-Image (S2I) Quickstart to OpenShift with Helm Charts

Log in to your OpenShift instance using the oc login command. The backend will be built and deployed on OpenShift with a Helm Chart for WildFly.

Navigate to the root directory of this quickstart and run the following command:

$ helm install microprofile-jwt -f charts/helm.yaml wildfly/wildfly --wait --timeout=10m0s
NAME: microprofile-jwt
...
STATUS: deployed
REVISION: 1

This command will return once the application has successfully deployed. In case of a timeout, you can check the status of the application with the following command in another terminal:

oc get deployment microprofile-jwt

The Helm Chart for this quickstart contains all the information to build an image from the source code using S2I on Java 17:

build:
  uri: https://github.com/wildfly/quickstart.git
  ref: main
  contextDir: microprofile-jwt
deploy:
  replicas: 1

This will create a new deployment on OpenShift and deploy the application.

If you want to see all the configuration elements to customize your deployment you can use the following command:

$ helm show readme wildfly/wildfly

Get the URL of the route to the deployment.

$ oc get route microprofile-jwt -o jsonpath="{.spec.host}"

Access the application in your web browser using the displayed URL.

Note

The Maven profile named openshift is used by the Helm chart to provision the server with the quickstart deployed on the root web context, and thus the application should be accessed with the URL without the /microprofile-jwt path segment after HOST:PORT.

Run the Integration Tests with OpenShift

The integration tests included with this quickstart, which verify that the quickstart runs correctly, may also be run with the quickstart running on OpenShift.

Note

The integration tests expect a deployed application, so make sure you have deployed the quickstart on OpenShift before you begin.

Run the integration tests using the following command to run the verify goal with the integration-testing profile activated and the proper URL:

$ mvn verify -Pintegration-testing -Dserver.host=https://$(oc get route microprofile-jwt --template='{{ .spec.host }}') 
Note

The tests are using SSL to connect to the quickstart running on OpenShift. So you need the certificates to be trusted by the machine the tests are run from.

Undeploy the WildFly Source-to-Image (S2I) Quickstart from OpenShift with Helm Charts

$ helm uninstall microprofile-jwt

Conclusion

As demonstrated within this Quickstart the MicroProfile JWT specification provides an easy way to secure deployments without needing to rely on managed server configuration. The techniques explored in this quickstart can be used to create your own MicroProfile JWT secured enpoints or to add MicroProfile JWT authentication to your existing deployments.