The microprofile-jwt
quickstart demonstrates the use of the MicroProfile JWT specification in WildFly.
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.
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.
The application this project produces is designed to be run on WildFly Application Server 35 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.
-
Open a terminal and navigate to the root of the WildFly directory.
-
Start the WildFly server with the MicroProfile profile by typing the following command.
$ WILDFLY_HOME/bin/standalone.sh -c standalone-microprofile.xml
NoteFor Windows, use the WILDFLY_HOME\bin\standalone.bat
script.
-
Make sure WildFly server is started.
-
Open a terminal and navigate to the root directory of this quickstart.
-
Type the following command to build the quickstart.
$ mvn clean package
-
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.
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 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.
-
Make sure WildFly server is started.
-
Make sure the quickstart is deployed.
-
Type the following command to run the
verify
goal with theintegration-testing
profile activated.$ mvn verify -Pintegration-testing
When you are finished testing the quickstart, follow these steps to undeploy the archive.
-
Make sure WildFly server is started.
-
Open a terminal and navigate to the root directory of this quickstart.
-
Type this command to undeploy the archive:
$ mvn wildfly:undeploy
In this section we will go through the steps to create a new Jakarta REST deployment from scratch and incrementally add support for MicroProfile JWT including token generation, activation of authorization and the injection of claims.
This quickstart will be making use of Jakarta REST 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.
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>
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 Jakarta REST 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 Jakarta REST 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 Jakarta REST 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.
-
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.
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
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.
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.
At this stage we have a basic Jakarta REST 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.
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 Jakarta REST 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
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.
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 activates the bootable JAR packaging when provisioning WildFly, through the <bootable-jar>true</bootable-jar>
configuration element:
<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 file is named microprofile-jwt-bootable.jar
, and may be found in the target
directory.
-
Ensure the bootable jar is built.
$ mvn clean clean package
-
Start the WildFly bootable jar use the WildFly Maven Plugin
start-jar
goal.$ mvn wildfly:start-jar
NoteYou may also start the bootable jar without Maven, using the
java
command.$ java -jar target/microprofile-jwt-bootable.jar
-
Run the integration tests use the
verify
goal, with theintegration-testing
profile activated.$ mvn verify -Pintegration-testing
-
Shut down the WildFly bootable jar use the WildFly Maven Plugin
shutdown
goal.$ mvn wildfly:shutdown
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
This section contains the basic instructions to build and deploy this quickstart to WildFly for OpenShift or WildFly for OpenShift Online using Helm Charts.
-
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
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.
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. |
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
This section contains the basic instructions to build and deploy this quickstart to Kubernetes using Helm Charts.
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"
-
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
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.
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
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.