Skip to content

Latest commit

 

History

History
2171 lines (1790 loc) · 80.9 KB

README.adoc

File metadata and controls

2171 lines (1790 loc) · 80.9 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 34 or later.

All you need to build this project is Java SE 17.0 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.

Build and Deploy the Quickstart

  1. Make sure WildFly server is started.

  2. Open a terminal and navigate to the root directory of this quickstart.

  3. Type the following command to build the quickstart.

    $ mvn clean package
  4. Type the following command to deploy the quickstart.

    $ mvn wildfly:deploy

This deploys the microprofile-jwt/target/microprofile-jwt.war.war to the running instance of the server.

You should see a message in the server log indicating that the archive deployed successfully.

Using the application

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.

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 

Undeploy the Quickstart

When you are finished testing the quickstart, follow these steps to undeploy the archive.

  1. Make sure WildFly server is started.

  2. Open a terminal and navigate to the root directory of this quickstart.

  3. Type this command to undeploy the archive:

    $ mvn wildfly:undeploy

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.expansion>{versionExpansionBom}</version.bom.expansion>
<version.bom.ee>{versionServerBom}</version.bom.ee>

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

<maven.compiler.release>17</maven.compiler.release>

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-expansion</artifactId>
      <version>${version.bom.expansion}</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 Maven Plugin 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>
          <activation>
              <activeByDefault>true</activeByDefault>
          </activation>
          <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>
                          <add-ons>...</add-ons>
                      </configuration>
                      <executions>
                          <execution>
                              <goals>
                                  <goal>package</goal>
                              </goals>
                          </execution>
                      </executions>
                  </plugin>
                  ...
              </plugins>
          </build>
      </profile>

The bootable-jar profile is activate by default, and when built the WildFly bootable jar may be found at target/microprofile-jwt-bootable.jar

Procedure
  1. Ensure the bootable jar is built.

    $ mvn clean clean package
  2. Start the WildFly bootable jar use the WildFly Maven Plugin start-jar goal.

    $ mvn wildfly:start-jar
  3. Run the integration tests use the verify goal, with the integration-testing profile activated.

    $ mvn verify -Pintegration-testing
  4. Shut down the WildFly bootable jar use the WildFly Maven Plugin shutdown goal.

    $ mvn wildfly: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>
                            <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.

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

Building and running the quickstart application with Kubernetes

Build the WildFly Quickstart to Kubernetes with Helm Charts

For Kubernetes, the build with Apache Maven uses an openshift Maven profile to provision a WildFly server, suitable for running on Kubernetes.

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>
                            <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 Kubernetes 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 Kubernetes and Helm Charts

This section contains the basic instructions to build and deploy this quickstart to Kubernetes using Helm Charts.

Install Kubernetes

In this example we are using Minikube as our Kubernetes provider. See the Minikube Getting Started guide for how to install it. After installing it, we start it with 4GB of memory.

minikube start --memory='4gb'

The above command should work if you have Docker installed on your machine. If, you are using Podman instead of Docker, you will also need to pass in --driver=podman, as covered in the Minikube documentation.

Once Minikube has started, we need to enable its registry since that is where we will push the image needed to deploy the quickstart, and where we will tell the Helm charts to download it from.

minikube addons enable registry

In order to be able to push images to the registry we need to make it accessible from outside Kubernetes. How we do this depends on your operating system. All the below examples will expose it at localhost:5000

# On Mac:
docker run --rm -it --network=host alpine ash -c "apk add socat && socat TCP-LISTEN:5000,reuseaddr,fork TCP:$(minikube ip):5000"

# On Linux:
kubectl port-forward --namespace kube-system service/registry 5000:80 &

# On Windows:
kubectl port-forward --namespace kube-system service/registry 5000:80
docker run --rm -it --network=host alpine ash -c "apk add socat && socat TCP-LISTEN:5000,reuseaddr,fork TCP:host.docker.internal:5000"

Prerequisites

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

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 Kubernetes with Helm Charts

The backend will be built and deployed on Kubernetes with a Helm Chart for WildFly.

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

mvn -Popenshift package wildfly:image

This will use the openshift Maven profile we saw earlier to build the application, and create a Docker image containing the WildFly server with the application deployed. The name of the image will be microprofile-jwt.

Next we need to tag the image and make it available to Kubernetes. You can push it to a registry like quay.io. In this case we tag as localhost:5000/microprofile-jwt:latest and push it to the internal registry in our Kubernetes instance:

# Tag the image
docker tag microprofile-jwt localhost:5000/microprofile-jwt:latest
# Push the image to the registry
docker push localhost:5000/microprofile-jwt:latest

In the below call to helm install which deploys our application to Kubernetes, we are passing in some extra arguments to tweak the Helm build:

  • --set build.enabled=false - This turns off the s2i build for the Helm chart since Kubernetes, unlike OpenShift, does not have s2i. Instead, we are providing the image to use.

  • --set deploy.route.enabled=false - This disables route creation normally performed by the Helm chart. On Kubernetes we will use port-forwards instead to access our application, since routes are an OpenShift specific concept and thus not available on Kubernetes.

  • --set image.name="localhost:5000/microprofile-jwt" - This tells the Helm chart to use the image we built, tagged and pushed to Kubernetes' internal registry above.

$ helm install microprofile-jwt -f charts/helm.yaml wildfly/wildfly --wait --timeout=10m0s --set build.enabled=false --set deploy.route.enabled=false --set image.name="localhost:5000/microprofile-jwt"
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:

kubectl 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 Kubernetes 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

To be able to connect to our application running in Kubernetes from outside, we need to set up a port-forward to the microprofile-jwt service created for us by the Helm chart.

This service will run on port 8080, and we set up the port forward to also run on port 8080:

kubectl port-forward service/microprofile-jwt 8080:8080

The server can now be accessed via http://localhost:8080 from outside Kubernetes. Note that the command to create the port-forward will not return, so it is easiest to run this in a separate terminal.

Run the Integration Tests with Kubernetes

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

Note

The integration tests expect a deployed application, so make sure you have deployed the quickstart on Kubernetes 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=http://localhost:8080 

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

$ helm uninstall microprofile-jwt

To stop the port forward you created earlier use:

$ kubectl port-forward service/microprofile-jwt 8080:8080

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.