Quarkus - Using JWT RBAC

This guide explains how your Quarkus application can utilize MicroProfile Json Web Token (JWT)Role-Based Access Control (RBAC) to providesecured access to the JAX-RS endpoints.

Solution

We recommend that you follow the instructions in the next sections and create the application step by step.However, you can skip right to the completed example.

Clone the Git repository: git clone https://github.com/quarkusio/quarkus-quickstarts.git, or download an archive.

The solution is located in the security-jwt-quickstart directory.

Creating the Maven project

First, we need a new project. Create a new project with the following command:

  1. mvn io.quarkus:quarkus-maven-plugin:1.0.0.CR1:create \
  2. -DprojectGroupId=org.acme \
  3. -DprojectArtifactId=security-jwt-quickstart \
  4. -DclassName="org.acme.jwt.TokenSecuredResource" \
  5. -Dpath="/secured" \
  6. -Dextensions="resteasy-jsonb, jwt"
  7. cd security-jwt-quickstart

This command generates the Maven project with a REST endpoint and imports the smallrye-jwt extension, which includes the MicroProfile JWT RBAC support.

Examine the JAX-RS resource

Open the src/main/java/org/acme/jwt/TokenSecuredResource.java file and see the following content:

Basic REST Endpoint

  1. package org.acme.jwt;
  2. import javax.ws.rs.GET;
  3. import javax.ws.rs.Path;
  4. import javax.ws.rs.Produces;
  5. import javax.ws.rs.core.MediaType;
  6. @Path("/secured")
  7. public class TokenSecuredResource {
  8. @GET
  9. @Produces(MediaType.TEXT_PLAIN)
  10. public String hello() {
  11. return "hello";
  12. }
  13. }

This is a basic REST endpoint that does not have any of the Smallrye JWT specific features, so let’s add some.

The MicroProfile JWT RBAC 1.1.1 specification details the annotations and behaviors we will make use of inthis quickstart. See HTML and PDF versions of the specification for the details.

REST Endpoint V1

  1. package org.acme.jwt;
  2. import java.security.Principal;
  3. import javax.annotation.security.PermitAll;
  4. import javax.inject.Inject;
  5. import javax.ws.rs.GET;
  6. import javax.ws.rs.Path;
  7. import javax.ws.rs.Produces;
  8. import javax.ws.rs.core.Context;
  9. import javax.ws.rs.core.MediaType;
  10. import javax.ws.rs.core.SecurityContext;
  11. import org.eclipse.microprofile.jwt.JsonWebToken;
  12. /**
  13. * Version 1 of the TokenSecuredResource
  14. */
  15. @Path("/secured")
  16. @RequestScoped (1)
  17. public class TokenSecuredResource {
  18. @Inject
  19. JsonWebToken jwt; (2)
  20. @GET()
  21. @Path("permit-all")
  22. @PermitAll (3)
  23. @Produces(MediaType.TEXT_PLAIN)
  24. public String hello(@Context SecurityContext ctx) { (4)
  25. Principal caller = ctx.getUserPrincipal(); (5)
  26. String name = caller == null ? "anonymous" : caller.getName();
  27. boolean hasJWT = jwt.getClaims() != null;
  28. String helloReply = String.format("hello + %s, isSecure: %s, authScheme: %s, hasJWT: %s", name, ctx.isSecure(), ctx.getAuthenticationScheme(), hasJWT);
  29. return helloReply; (6)
  30. }
  31. }
1Add a RequestScoped as Quarkus uses a default scoping of ApplicationScoped and thiswill produce undesirable behavior since JWT claims are naturally request scoped.
2Here we inject the JsonWebToken interface, an extension of the java.security.Principal interface that provides access to the claims associated with the current authenticated token.
3@PermitAll is a JSR 250 common security annotation that indicates that the given endpoint is accessible by any caller, authenticated or not.
4Here we inject the JAX-RS SecurityContext to inspect the security state of the call.
5Here we obtain the current request user/caller Principal. For an unsecured call this will be null, so we build the user name by checking caller against null.
6The reply we build up makes use of the caller name, the isSecure() and getAuthenticationScheme() states of the request SecurityContext, and whether a non-null JsonWebToken was injected.

Run the application

Now we are ready to run our application. Use:

  1. ./mvnw compile quarkus:dev

and you should see output similar to:

quarkus:dev Output

  1. $ ./mvnw compile quarkus:dev
  2. [INFO] Scanning for projects...
  3. [INFO]
  4. [INFO] ----------------------< org.acme:security-jwt-quickstart >-----------------------
  5. [INFO] Building security-jwt-quickstart 1.0-SNAPSHOT
  6. [INFO] --------------------------------[ jar ]---------------------------------
  7. ...
  8. Listening for transport dt_socket at address: 5005
  9. 2019-03-03 07:23:06,988 INFO [io.qua.dep.QuarkusAugmentor] (main) Beginning quarkus augmentation
  10. 2019-03-03 07:23:07,328 INFO [io.qua.dep.QuarkusAugmentor] (main) Quarkus augmentation completed in 340ms
  11. 2019-03-03 07:23:07,493 INFO [io.quarkus] (main) Quarkus started in 0.769s. Listening on: http://127.0.0.1:8080
  12. 2019-03-03 07:23:07,493 INFO [io.quarkus] (main) Installed features: [cdi, resteasy, resteasy-jsonb, security, smallrye-jwt, vertx, vertx-web]

Now that the REST endpoint is running, we can access it using a command line tool like curl:

curl command for /secured/permit-all

  1. $ curl http://127.0.0.1:8080/secured/permit-all; echo
  2. hello + anonymous, isSecure: false, authScheme: null, hasJWT: false

We have not provided any JWT in our request, so we would not expect that there is any security state seen by the endpoint, andthe response is consistent with that:

  • user name is anonymous

  • isSecure is false as https is not used

  • authScheme is null

  • hasJWT is false

Use Ctrl-C to stop the Quarkus server.

So now let’s actually secure something. Take a look at the new endpoint method helloRolesAllowed in the following:

REST Endpoint V2

  1. package org.acme.jwt;
  2. import java.security.Principal;
  3. import javax.annotation.security.PermitAll;
  4. import javax.annotation.security.RolesAllowed;
  5. import javax.inject.Inject;
  6. import javax.ws.rs.GET;
  7. import javax.ws.rs.Path;
  8. import javax.ws.rs.Produces;
  9. import javax.ws.rs.core.Context;
  10. import javax.ws.rs.core.MediaType;
  11. import javax.ws.rs.core.SecurityContext;
  12. import org.eclipse.microprofile.jwt.JsonWebToken;
  13. /**
  14. * Version 2 of the TokenSecuredResource
  15. */
  16. @Path("/secured")
  17. @RequestScoped
  18. public class TokenSecuredResource {
  19. @Inject
  20. JsonWebToken jwt;
  21. @GET()
  22. @Path("permit-all")
  23. @PermitAll
  24. @Produces(MediaType.TEXT_PLAIN)
  25. public String hello(@Context SecurityContext ctx) {
  26. Principal caller = ctx.getUserPrincipal();
  27. String name = caller == null ? "anonymous" : caller.getName();
  28. String helloReply = String.format("hello + %s, isSecure: %s, authScheme: %s", name, ctx.isSecure(), ctx.getAuthenticationScheme());
  29. return helloReply;
  30. }
  31. @GET()
  32. @Path("roles-allowed") (1)
  33. @RolesAllowed({"Echoer", "Subscriber"}) (2)
  34. @Produces(MediaType.TEXT_PLAIN)
  35. public String helloRolesAllowed(@Context SecurityContext ctx) {
  36. Principal caller = ctx.getUserPrincipal();
  37. String name = caller == null ? "anonymous" : caller.getName();
  38. boolean hasJWT = jwt.getClaimNames() != null;
  39. String helloReply = String.format("hello + %s, isSecure: %s, authScheme: %s, hasJWT: %s", name, ctx.isSecure(), ctx.getAuthenticationScheme(), hasJWT);
  40. return helloReply;
  41. }
  42. }
1This new endpoint will be located at /secured/roles-allowed
2@RolesAllowed is a JSR 250 common security annotation that indicates that the given endpoint is accessible by a caller ifthey have either a "Echoer" or "Subscriber" role assigned.

After you make this addition to your TokenSecuredResource, rerun the ./mvnw compile quarkus:dev command, and then try curl -v http://127.0.0.1:8080/secured/roles-allowed; echo to attempt to access the new endpoint. Your output should be:

curl command for /secured/roles-allowed

  1. $ curl -v http://127.0.0.1:8080/secured/roles-allowed; echo
  2. * Trying 127.0.0.1...
  3. * TCP_NODELAY set
  4. * Connected to 127.0.0.1 (127.0.0.1) port 8080 (#0)
  5. > GET /secured/roles-allowed HTTP/1.1
  6. > Host: 127.0.0.1:8080
  7. > User-Agent: curl/7.54.0
  8. > Accept: */*
  9. >
  10. < HTTP/1.1 401 Unauthorized
  11. < Connection: keep-alive
  12. < Content-Type: text/html;charset=UTF-8
  13. < Content-Length: 14
  14. < Date: Sun, 03 Mar 2019 16:32:34 GMT
  15. <
  16. * Connection #0 to host 127.0.0.1 left intact
  17. Not authorized

Excellent, we have not provided any JWT in the request, so we should not be able to access the endpoint, and we were not. Instead we received an HTTP 401 Unauthorized error. We need to obtain and pass in a valid JWT to access that endpoint. There are two steps to this, 1) configuring our Smallrye JWT extension with information on how to validate a JWT, and 2) generating a matching JWT with the appropriate claims.

Configuring the Smallrye JWT Extension Security Information

In the Configuration Reference section we introduce the application.properties file that affect the Smallrye JWT extension.

Setting up application.properties

For part A of step 1, create a security-jwt-quickstart/src/main/resources/application.properties with the following content:

application.properties for TokenSecuredResource

  1. mp.jwt.verify.publickey.location=META-INF/resources/publicKey.pem (1)
  2. mp.jwt.verify.issuer=https://quarkus.io/using-jwt-rbac (2)
  3. quarkus.smallrye-jwt.auth-mechanism=MP-JWT (3)
  4. quarkus.smallrye-jwt.enabled=true (4)
1We are setting public key location to point to a classpath publicKey.pem resource location. We will add this key in part B, Adding a Public Key.
2We are setting the issuer to the URL string https://quarkus.io/using-jwt-rbac.
3We are setting the authentication mechanism name to MP-JWT. This is not strictly required to allow our quickstart to work, but it is the MicroProfile JWT RBAC specification standard name for the token based authentication mechanism.
4We are enabling the Smallrye JWT. Also not required since this is the default,but we are making it explicit.

Adding a Public Key

The JWT specification defines various levels of security of JWTs that one can use.The MicroProfile JWT RBAC specification requires that JWTs that are signed with the RSA-256 signature algorithm. This inturn requires a RSA public key pair. On the REST endpoint server side, you need to configure the location of the RSA publickey to use to verify the JWT sent along with requests. The mp.jwt.verify.publickey.location=publicKey.pem setting configuredpreviously expects that the public key is available on the classpath as publicKey.pem. To accomplish this, copy the followingcontent to a security-jwt-quickstart/src/main/resources/META-INF/resources/publicKey.pem file.

RSA Public Key PEM Content

  1. -----BEGIN PUBLIC KEY-----
  2. MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlivFI8qB4D0y2jy0CfEq
  3. Fyy46R0o7S8TKpsx5xbHKoU1VWg6QkQm+ntyIv1p4kE1sPEQO73+HY8+Bzs75XwR
  4. TYL1BmR1w8J5hmjVWjc6R2BTBGAYRPFRhor3kpM6ni2SPmNNhurEAHw7TaqszP5e
  5. UF/F9+KEBWkwVta+PZ37bwqSE4sCb1soZFrVz/UT/LF4tYpuVYt3YbqToZ3pZOZ9
  6. AX2o1GCG3xwOjkc4x0W7ezbQZdC9iftPxVHR8irOijJRRjcPDtA6vPKpzLl6CyYn
  7. sIYPd99ltwxTHjr3npfv/3Lw50bAkbT4HeLFxTx4flEoZLKO/g0bAoV2uqBhkA9x
  8. nQIDAQAB
  9. -----END PUBLIC KEY-----

Generating a JWT

Often one obtains a JWT from an identity manager like Keycloak, but for this quickstart we will generate our own using theJose4J library and the TokenUtils class shown in the following listing. Take this source and place it into security-jwt-quickstart/src/test/java/org/acme/jwt/TokenUtils.java.

JWT libraries for many different programming languages can be found at the JWT.io website JWT Libraries.

JWT utility class

  1. package org.acme.jwt;
  2. import java.io.BufferedReader;
  3. import java.io.IOException;
  4. import java.io.InputStream;
  5. import java.io.InputStreamReader;
  6. import java.security.KeyFactory;
  7. import java.security.PrivateKey;
  8. import java.security.spec.PKCS8EncodedKeySpec;
  9. import java.util.Base64;
  10. import java.util.Map;
  11. import java.util.stream.Collectors;
  12. import org.eclipse.microprofile.jwt.Claims;
  13. import org.jose4j.jws.AlgorithmIdentifiers;
  14. import org.jose4j.jws.JsonWebSignature;
  15. import org.jose4j.jwt.JwtClaims;
  16. import org.jose4j.jwt.NumericDate;
  17. /**
  18. * Utilities for generating a JWT for testing
  19. */
  20. public class TokenUtils {
  21. private TokenUtils() {
  22. // no-op: utility class
  23. }
  24. /**
  25. * Utility method to generate a JWT string from a JSON resource file that is signed by the privateKey.pem
  26. * test resource key, possibly with invalid fields.
  27. *
  28. * @param jsonResName - name of test resources file
  29. * @param timeClaims - used to return the exp, iat, auth_time claims
  30. * @return the JWT string
  31. * @throws Exception on parse failure
  32. */
  33. public static String generateTokenString(String jsonResName, Map<String, Long> timeClaims)
  34. throws Exception {
  35. // Use the test private key associated with the test public key for a valid signature
  36. PrivateKey pk = readPrivateKey("/privateKey.pem");
  37. return generateTokenString(pk, "/privateKey.pem", jsonResName, timeClaims);
  38. }
  39. public static String generateTokenString(PrivateKey privateKey, String kid,
  40. String jsonResName, Map<String, Long> timeClaims) throws Exception {
  41. JwtClaims claims = JwtClaims.parse(readTokenContent(jsonResName));
  42. long currentTimeInSecs = currentTimeInSecs();
  43. long exp = timeClaims != null && timeClaims.containsKey(Claims.exp.name())
  44. ? timeClaims.get(Claims.exp.name()) : currentTimeInSecs + 300;
  45. claims.setIssuedAt(NumericDate.fromSeconds(currentTimeInSecs));
  46. claims.setClaim(Claims.auth_time.name(), NumericDate.fromSeconds(currentTimeInSecs));
  47. claims.setExpirationTime(NumericDate.fromSeconds(exp));
  48. for (Map.Entry<String, Object> entry : claims.getClaimsMap().entrySet()) {
  49. System.out.printf("\tAdded claim: %s, value: %s\n", entry.getKey(), entry.getValue());
  50. }
  51. JsonWebSignature jws = new JsonWebSignature();
  52. jws.setPayload(claims.toJson());
  53. jws.setKey(privateKey);
  54. jws.setKeyIdHeaderValue(kid);
  55. jws.setHeader("typ", "JWT");
  56. jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256);
  57. return jws.getCompactSerialization();
  58. }
  59. private static String readTokenContent(String jsonResName) throws IOException {
  60. InputStream contentIS = TokenUtils.class.getResourceAsStream(jsonResName);
  61. try (BufferedReader buffer = new BufferedReader(new InputStreamReader(contentIS))) {
  62. return buffer.lines().collect(Collectors.joining("\n"));
  63. }
  64. }
  65. /**
  66. * Read a PEM encoded private key from the classpath
  67. *
  68. * @param pemResName - key file resource name
  69. * @return PrivateKey
  70. * @throws Exception on decode failure
  71. */
  72. public static PrivateKey readPrivateKey(final String pemResName) throws Exception {
  73. InputStream contentIS = TokenUtils.class.getResourceAsStream(pemResName);
  74. byte[] tmp = new byte[4096];
  75. int length = contentIS.read(tmp);
  76. return decodePrivateKey(new String(tmp, 0, length, "UTF-8"));
  77. }
  78. /**
  79. * Decode a PEM encoded private key string to an RSA PrivateKey
  80. *
  81. * @param pemEncoded - PEM string for private key
  82. * @return PrivateKey
  83. * @throws Exception on decode failure
  84. */
  85. public static PrivateKey decodePrivateKey(final String pemEncoded) throws Exception {
  86. byte[] encodedBytes = toEncodedBytes(pemEncoded);
  87. PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encodedBytes);
  88. KeyFactory kf = KeyFactory.getInstance("RSA");
  89. return kf.generatePrivate(keySpec);
  90. }
  91. private static byte[] toEncodedBytes(final String pemEncoded) {
  92. final String normalizedPem = removeBeginEnd(pemEncoded);
  93. return Base64.getDecoder().decode(normalizedPem);
  94. }
  95. private static String removeBeginEnd(String pem) {
  96. pem = pem.replaceAll("-----BEGIN (.*)-----", "");
  97. pem = pem.replaceAll("-----END (.*)----", "");
  98. pem = pem.replaceAll("\r\n", "");
  99. pem = pem.replaceAll("\n", "");
  100. return pem.trim();
  101. }
  102. /**
  103. * @return the current time in seconds since epoch
  104. */
  105. public static int currentTimeInSecs() {
  106. long currentTimeMS = System.currentTimeMillis();
  107. return (int) (currentTimeMS / 1000);
  108. }
  109. }

Next take the code from the following listing and place into security-jwt-quickstart/src/test/java/org/acme/jwt/GenerateToken.java:

GenerateToken main Driver Class

  1. package org.acme.jwt;
  2. import java.util.HashMap;
  3. import org.eclipse.microprofile.jwt.Claims;
  4. /**
  5. * A simple utility class to generate and print a JWT token string to stdout. Can be run with:
  6. * mvn exec:java -Dexec.mainClass=org.acme.jwt.GenerateToken -Dexec.classpathScope=test
  7. */
  8. public class GenerateToken {
  9. /**
  10. *
  11. * @param args - [0]: optional name of classpath resource for json document of claims to add; defaults to "/JwtClaims.json"
  12. * [1]: optional time in seconds for expiration of generated token; defaults to 300
  13. * @throws Exception
  14. */
  15. public static void main(String[] args) throws Exception {
  16. String claimsJson = "/JwtClaims.json";
  17. if (args.length > 0) {
  18. claimsJson = args[0];
  19. }
  20. HashMap<String, Long> timeClaims = new HashMap<>();
  21. if (args.length > 1) {
  22. long duration = Long.parseLong(args[1]);
  23. long exp = TokenUtils.currentTimeInSecs() + duration;
  24. timeClaims.put(Claims.exp.name(), exp);
  25. }
  26. String token = TokenUtils.generateTokenString(claimsJson, timeClaims);
  27. System.out.println(token);
  28. }
  29. }

Now we need the content of the RSA private key that corresponds to the public key we have in the TokenSecuredResource application. Take the following PEM content and place it into security-jwt-quickstart/src/test/resources/privateKey.pem.

RSA Private Key PEM Content

  1. -----BEGIN PRIVATE KEY-----
  2. MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCWK8UjyoHgPTLa
  3. PLQJ8SoXLLjpHSjtLxMqmzHnFscqhTVVaDpCRCb6e3Ii/WniQTWw8RA7vf4djz4H
  4. OzvlfBFNgvUGZHXDwnmGaNVaNzpHYFMEYBhE8VGGiveSkzqeLZI+Y02G6sQAfDtN
  5. qqzM/l5QX8X34oQFaTBW1r49nftvCpITiwJvWyhkWtXP9RP8sXi1im5Vi3dhupOh
  6. nelk5n0BfajUYIbfHA6ORzjHRbt7NtBl0L2J+0/FUdHyKs6KMlFGNw8O0Dq88qnM
  7. uXoLJiewhg9332W3DFMeOveel+//cvDnRsCRtPgd4sXFPHh+UShkso7+DRsChXa6
  8. oGGQD3GdAgMBAAECggEAAjfTSZwMHwvIXIDZB+yP+pemg4ryt84iMlbofclQV8hv
  9. 6TsI4UGwcbKxFOM5VSYxbNOisb80qasb929gixsyBjsQ8284bhPJR7r0q8h1C+jY
  10. URA6S4pk8d/LmFakXwG9Tz6YPo3pJziuh48lzkFTk0xW2Dp4SLwtAptZY/+ZXyJ6
  11. 96QXDrZKSSM99Jh9s7a0ST66WoxSS0UC51ak+Keb0KJ1jz4bIJ2C3r4rYlSu4hHB
  12. Y73GfkWORtQuyUDa9yDOem0/z0nr6pp+pBSXPLHADsqvZiIhxD/O0Xk5I6/zVHB3
  13. zuoQqLERk0WvA8FXz2o8AYwcQRY2g30eX9kU4uDQAQKBgQDmf7KGImUGitsEPepF
  14. KH5yLWYWqghHx6wfV+fdbBxoqn9WlwcQ7JbynIiVx8MX8/1lLCCe8v41ypu/eLtP
  15. iY1ev2IKdrUStvYRSsFigRkuPHUo1ajsGHQd+ucTDf58mn7kRLW1JGMeGxo/t32B
  16. m96Af6AiPWPEJuVfgGV0iwg+HQKBgQCmyPzL9M2rhYZn1AozRUguvlpmJHU2DpqS
  17. 34Q+7x2Ghf7MgBUhqE0t3FAOxEC7IYBwHmeYOvFR8ZkVRKNF4gbnF9RtLdz0DMEG
  18. 5qsMnvJUSQbNB1yVjUCnDAtElqiFRlQ/k0LgYkjKDY7LfciZl9uJRl0OSYeX/qG2
  19. tRW09tOpgQKBgBSGkpM3RN/MRayfBtmZvYjVWh3yjkI2GbHA1jj1g6IebLB9SnfL
  20. WbXJErCj1U+wvoPf5hfBc7m+jRgD3Eo86YXibQyZfY5pFIh9q7Ll5CQl5hj4zc4Y
  21. b16sFR+xQ1Q9Pcd+BuBWmSz5JOE/qcF869dthgkGhnfVLt/OQzqZluZRAoGAXQ09
  22. nT0TkmKIvlza5Af/YbTqEpq8mlBDhTYXPlWCD4+qvMWpBII1rSSBtftgcgca9XLB
  23. MXmRMbqtQeRtg4u7dishZVh1MeP7vbHsNLppUQT9Ol6lFPsd2xUpJDc6BkFat62d
  24. Xjr3iWNPC9E9nhPPdCNBv7reX7q81obpeXFMXgECgYEAmk2Qlus3OV0tfoNRqNpe
  25. Mb0teduf2+h3xaI1XDIzPVtZF35ELY/RkAHlmWRT4PCdR0zXDidE67L6XdJyecSt
  26. FdOUH8z5qUraVVebRFvJqf/oGsXc4+ex1ZKUTbY0wqY1y9E39yvB3MaTmZFuuqk8
  27. f3cg+fr8aou7pr9SHhJlZCU=
  28. -----END PRIVATE KEY-----

And finally, we need to define what claims to include in the JWT. The TokenUtils class uses a json resource on the classpathto define the non-time sensitive claims, so take the content from the following listing and place it intosecurity-jwt-quickstart/src/test/resources/JwtClaims.json:

JwtClaims.json claims document

  1. {
  2. "iss": "https://quarkus.io/using-jwt-rbac",
  3. "jti": "a-123",
  4. "sub": "jdoe-using-jwt-rbac",
  5. "upn": "jdoe@quarkus.io",
  6. "preferred_username": "jdoe",
  7. "aud": "using-jwt-rbac",
  8. "birthdate": "2001-07-13",
  9. "roleMappings": {
  10. "group1": "Group1MappedRole",
  11. "group2": "Group2MappedRole"
  12. },
  13. "groups": [
  14. "Echoer",
  15. "Tester",
  16. "Subscriber",
  17. "group2"
  18. ]
  19. }

Let’s explore the content of this document in more detail to understand how the claims will affect our application security.

JwtClaims.json claims document

  1. {
  2. "iss": "https://quarkus.io/using-jwt-rbac", (1)
  3. "jti": "a-123",
  4. "sub": "jdoe-using-jwt-rbac",
  5. "upn": "jdoe@quarkus.io", (2)
  6. "preferred_username": "jdoe",
  7. "aud": "using-jwt-rbac",
  8. "birthdate": "2001-07-13",
  9. "roleMappings": { (3)
  10. "group1": "Group1MappedRole",
  11. "group2": "Group2MappedRole"
  12. },
  13. "groups": [ (4)
  14. "Echoer",
  15. "Tester",
  16. "Subscriber",
  17. "group2"
  18. ]
  19. }
1The iss claim is the issuer of the JWT. This needs to match the server side mp.jwt.verify.issuerin order for the token to be accepted as valid.
2The upn claim is defined by the MicroProfile JWT RBAC spec as preferred claim to use for thePrincipal seen via the container security APIs.
3The roleMappings claim can be used to map from a role defined in the groups claimto an application level role defined in a @RolesAllowed annotation. We won’t use thisfeature in this quickstart, but it can be useful when the IDM providing the token hasroles that do not directly align with those defined by the application.
4The group claim provides the groups and top-level roles associated with the JWT bearer.In this quickstart we are only using the top-level role mapping which means the JWT willbe seen to have the roles "Echoer", "Tester", "Subscriber" and "group2". The full set of roles wouldalso include a "Group2MappedRole" due to the roleMappings claim having a mapping from"group2" to "Group2MappedRole".

Now we can generate a JWT to use with TokenSecuredResource endpoint. To do this, run the following command:

Command to Generate JWT

  1. mvn exec:java -Dexec.mainClass=org.acme.jwt.GenerateToken -Dexec.classpathScope=test
You may need to run ./mvnw test-compile before this if you are working strictly from the command line and not an IDE thatautomatically compiles code as you write it.

Sample JWT Generation Output

  1. $ mvn exec:java -Dexec.mainClass=org.acme.jwt.GenerateToken -Dexec.classpathScope=test
  2. [INFO] Scanning for projects...
  3. [INFO]
  4. [INFO] ----------------------< org.acme:security-jwt-quickstart >-----------------------
  5. [INFO] Building security-jwt-quickstart 1.0-SNAPSHOT
  6. [INFO] --------------------------------[ jar ]---------------------------------
  7. [INFO]
  8. [INFO] --- exec-maven-plugin:1.6.0:java (default-cli) @ security-jwt-quickstart ---
  9. Setting exp: 1551659976 / Sun Mar 03 16:39:36 PST 2019
  10. Added claim: sub, value: jdoe-using-jwt-rbac
  11. Added claim: aud, value: [using-jwt-rbac]
  12. Added claim: upn, value: jdoe@quarkus.io
  13. Added claim: birthdate, value: 2001-07-13
  14. Added claim: auth_time, value: 1551659676
  15. Added claim: iss, value: https://quarkus.io/using-jwt-rbac
  16. Added claim: roleMappings, value: {"group2":"Group2MappedRole","group1":"Group1MappedRole"}
  17. Added claim: groups, value: ["Echoer","Tester","Subscriber","group2"]
  18. Added claim: preferred_username, value: jdoe
  19. Added claim: exp, value: Sun Mar 03 16:39:36 PST 2019
  20. Added claim: iat, value: Sun Mar 03 16:34:36 PST 2019
  21. Added claim: jti, value: a-123
  22. eyJraWQiOiJcL3ByaXZhdGVLZXkucGVtIiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiJqZG9lLXVzaW5nLWp3dC1yYmFjIiwiYXVkIjoidXNpbmctand0LXJiYWMiLCJ1cG4iOiJqZG9lQHF1YXJrdXMuaW8iLCJiaXJ0aGRhdGUiOiIyMDAxLTA3LTEzIiwiYXV0aF90aW1lIjoxNTUxNjU5Njc2LCJpc3MiOiJodHRwczpcL1wvcXVhcmt1cy5pb1wvdXNpbmctand0LXJiYWMiLCJyb2xlTWFwcGluZ3MiOnsiZ3JvdXAyIjoiR3JvdXAyTWFwcGVkUm9sZSIsImdyb3VwMSI6Ikdyb3VwMU1hcHBlZFJvbGUifSwiZ3JvdXBzIjpbIkVjaG9lciIsIlRlc3RlciIsIlN1YnNjcmliZXIiLCJncm91cDIiXSwicHJlZmVycmVkX3VzZXJuYW1lIjoiamRvZSIsImV4cCI6MTU1MTY1OTk3NiwiaWF0IjoxNTUxNjU5Njc2LCJqdGkiOiJhLTEyMyJ9.O9tx_wNNS4qdpFhxeD1e7v4aBNWz1FCq0UV8qmXd7dW9xM4hA5TO-ZREk3ApMrL7_rnX8z81qGPIo_R8IfHDyNaI1SLD56gVX-NaOLS2OjfcbO3zOWJPKR_BoZkYACtMoqlWgIwIRC-wJKUJU025dHZiNL0FWO4PjwuCz8hpZYXIuRscfFhXKrDX1fh3jDhTsOEFfu67ACd85f3BdX9pe-ayKSVLh_RSbTbBPeyoYPE59FW7H5-i8IE-Gqu838Hz0i38ksEJFI25eR-AJ6_PSUD0_-TV3NjXhF3bFIeT4VSaIZcpibekoJg0cQm-4ApPEcPLdgTejYHA-mupb8hSwg
  23. [INFO] ------------------------------------------------------------------------
  24. [INFO] BUILD SUCCESS
  25. [INFO] ------------------------------------------------------------------------
  26. [INFO] Total time: 1.682 s
  27. [INFO] Finished at: 2019-03-03T16:34:36-08:00
  28. [INFO] ------------------------------------------------------------------------

The JWT string is the base64 encoded string that has 3 parts separated by '.' characters:eyJraWQiOiJcL3ByaXZhdGVLZXkucGVtIiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiJqZG9lLXVzaW5nLWp3dC1yYmFjIiwiYXVkIjoidXNpbmctand0LXJiYWMiLCJ1cG4iOiJqZG9lQHF1YXJrdXMuaW8iLCJiaXJ0aGRhdGUiOiIyMDAxLTA3LTEzIiwiYXV0aF90aW1lIjoxNTUxNjUyMDkxLCJpc3MiOiJodHRwczpcL1wvcXVhcmt1cy5pb1wvdXNpbmctand0LXJiYWMiLCJyb2xlTWFwcGluZ3MiOnsiZ3JvdXAyIjoiR3JvdXAyTWFwcGVkUm9sZSIsImdyb3VwMSI6Ikdyb3VwMU1hcHBlZFJvbGUifSwiZ3JvdXBzIjpbIkVjaG9lciIsIlRlc3RlciIsIlN1YnNjcmliZXIiLCJncm91cDIiXSwicHJlZmVycmVkX3VzZXJuYW1lIjoiamRvZSIsImV4cCI6MTU1MTY1MjM5MSwiaWF0IjoxNTUxNjUyMDkxLCJqdGkiOiJhLTEyMyJ9.aPA4Rlc4kw7n_OZZRRk25xZydJy_J_3BRR8ryYLyHTO1o68_aNWWQCgpnAuOW64svPhPnLYYnQzK-l2vHX34B64JySyBD4y_vRObGmdwH_SEufBAWZV7mkG3Y4mTKT3_4EWNu4VH92IhdnkGI4GJB6yHAEzlQI6EdSOa4Nq8Gp4uPGqHsUZTJrA3uIW0TbNshFBm47-oVM3ZUrBz57JKtr0e9jv0HjPQWyvbzx1HuxZd6eA8ow8xzvooKXFxoSFCMnxotd3wagvYQ9ysBa89bgzL-lhjWtusuMFDUVYwFqADE7oOSOD4Vtclgq8svznBQ-YpfTHfb9QEcofMlpyjNA

If you start playing around with the code and/or the solution code, you will only be ableto use a given token for 5-6 minutes because that is the default expiration period + grace period. To usea longer expiration, pass in the lifetime of the token in seconds as the second argument to the GenerateToken class using-Dexec.args=…​. The first argument is the classpath resource name of the json document containing the claims to add tothe JWT, and should be '/JwtClaims.json' for this quickstart.

Example Command to Generate JWT with Lifetime of 3600 Seconds

  1. $ mvn exec:java -Dexec.mainClass=org.acme.jwt.GenerateToken -Dexec.classpathScope=test -Dexec.args="/JwtClaims.json 3600"
  2. [INFO] Scanning for projects...
  3. [INFO]
  4. [INFO] ----------------------< org.acme: >-----------------------
  5. [INFO] Building security-jwt-quickstart 1.0-SNAPSHOT
  6. [INFO] --------------------------------[ jar ]---------------------------------
  7. [INFO]
  8. [INFO] --- exec-maven-plugin:1.6.0:java (default-cli) @ security-jwt-quickstart ---
  9. Added claim: iss, value: https://quarkus.io/using-jwt-rbac
  10. Added claim: jti, value: a-123
  11. Added claim: sub, value: jdoe-using-jwt-rbac
  12. Added claim: upn, value: jdoe@quarkus.io
  13. Added claim: preferred_username, value: jdoe
  14. Added claim: aud, value: using-jwt-rbac
  15. Added claim: birthdate, value: 2001-07-13
  16. Added claim: roleMappings, value: {group1=Group1MappedRole, group2=Group2MappedRole}
  17. Added claim: groups, value: [Echoer, Tester, Subscriber, group2]
  18. Added claim: iat, value: 1571329458
  19. Added claim: auth_time, value: NumericDate{1571329458 -> Oct 17, 2019 5:24:18 PM IST}
  20. Added claim: exp, value: 1571333058
  21. eyJraWQiOiIvcHJpdmF0ZUtleS5wZW0iLCJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL3F1YXJrdXMuaW8vdXNpbmctand0LXJiYWMiLCJqdGkiOiJhLTEyMyIsInN1YiI6Impkb2UtdXNpbmctand0LXJiYWMiLCJ1cG4iOiJqZG9lQHF1YXJrdXMuaW8iLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJqZG9lIiwiYXVkIjoidXNpbmctand0LXJiYWMiLCJiaXJ0aGRhdGUiOiIyMDAxLTA3LTEzIiwicm9sZU1hcHBpbmdzIjp7Imdyb3VwMSI6Ikdyb3VwMU1hcHBlZFJvbGUiLCJncm91cDIiOiJHcm91cDJNYXBwZWRSb2xlIn0sImdyb3VwcyI6WyJFY2hvZXIiLCJUZXN0ZXIiLCJTdWJzY3JpYmVyIiwiZ3JvdXAyIl0sImlhdCI6MTU3MTMyOTQ1OCwiYXV0aF90aW1lIjoiTnVtZXJpY0RhdGV7MTU3MTMyOTQ1OCAtPiBPY3QgMTcsIDIwMTkgNToyNDoxOCBQTSBJU1R9IiwiZXhwIjoxNTcxMzMzMDU4fQ.Hn6f0qSk6wbbqOM-q9zo1KQ91VwIAdhJqdMmNK3pQrgSv68Ljdi75nSKvDmQwhtvEnHbZvoZy4BqbQagLT05JYcAWaT4NrtFLaqtJ_k8HD39_HosObF43u-vpEwisen0U219R0hpo9jx8Qohj4gzM-YL1sIFgqZSgsxH6YEorVLS70vkizTqfcclMvyrmkUq0nA4p4ST7jq987RkqXtY7U6jNc0rVnu7XmalA26VtfcqSgz9fwk_b-TmwqA6jgLvO6Rdovh0Q6tRDOW1VugQ_11-3k34ImdD3HG8gpdGatulHKWoxg9MhIcbrFWftlk7Ts97tkljp8ysfFzwFELnkg
  22. [INFO] ------------------------------------------------------------------------
  23. [INFO] BUILD SUCCESS
  24. [INFO] ------------------------------------------------------------------------
  25. [INFO] Total time: 1.685 s
  26. [INFO] Finished at: 2019-03-03T16:32:35-08:00
  27. [INFO] ------------------------------------------------------------------------

Finally, Secured Access to /secured/roles-allowed

Now let’s use this to make a secured request to the /secured/roles-allowed endpoint. Make sure you have the Quarkus server running using the ./mvnw compile quarkus:dev command, and then run the following command, making sure to use your version of the generated JWT from the previous step:

  1. curl -H "Authorization: Bearer eyJraWQiOiJcL3ByaXZhdGVLZXkucGVtIiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiJqZG9lLXVzaW5nLWp3dC1yYmFjIiwiYXVkIjoidXNpbmctand0LXJiYWMiLCJ1cG4iOiJqZG9lQHF1YXJrdXMuaW8iLCJiaXJ0aGRhdGUiOiIyMDAxLTA3LTEzIiwiYXV0aF90aW1lIjoxNTUxNjUyMDkxLCJpc3MiOiJodHRwczpcL1wvcXVhcmt1cy5pb1wvdXNpbmctand0LXJiYWMiLCJyb2xlTWFwcGluZ3MiOnsiZ3JvdXAyIjoiR3JvdXAyTWFwcGVkUm9sZSIsImdyb3VwMSI6Ikdyb3VwMU1hcHBlZFJvbGUifSwiZ3JvdXBzIjpbIkVjaG9lciIsIlRlc3RlciIsIlN1YnNjcmliZXIiLCJncm91cDIiXSwicHJlZmVycmVkX3VzZXJuYW1lIjoiamRvZSIsImV4cCI6MTU1MTY1MjM5MSwiaWF0IjoxNTUxNjUyMDkxLCJqdGkiOiJhLTEyMyJ9.aPA4Rlc4kw7n_OZZRRk25xZydJy_J_3BRR8ryYLyHTO1o68_aNWWQCgpnAuOW64svPhPnLYYnQzK-l2vHX34B64JySyBD4y_vRObGmdwH_SEufBAWZV7mkG3Y4mTKT3_4EWNu4VH92IhdnkGI4GJB6yHAEzlQI6EdSOa4Nq8Gp4uPGqHsUZTJrA3uIW0TbNshFBm47-oVM3ZUrBz57JKtr0e9jv0HjPQWyvbzx1HuxZd6eA8ow8xzvooKXFxoSFCMnxotd3wagvYQ9ysBa89bgzL-lhjWtusuMFDUVYwFqADE7oOSOD4Vtclgq8svznBQ-YpfTHfb9QEcofMlpyjNA" http://127.0.0.1:8080/secured/roles-allowed; echo

curl Command for /secured/roles-allowed With JWT

  1. $ curl -H "Authorization: Bearer eyJraWQ..." http://127.0.0.1:8080/secured/roles-allowed; echo
  2. hello + jdoe@quarkus.io, isSecure: false, authScheme: MP-JWT, hasJWT: true

Success! We now have:

  • a non-anonymous caller name of jdoe@quarkus.io

  • an authentication scheme of Bearer

  • a non-null JsonWebToken

Using the JsonWebToken and Claim Injection

Now that we can generate a JWT to access our secured REST endpoints, let’s see what more we can do with the JsonWebTokeninterface and the JWT claims. The org.eclipse.microprofile.jwt.JsonWebToken interface extends the java.security.Principalinterface, and is in fact the type of the object that is returned by the javax.ws.rs.core.SecurityContext#getUserPrincipal() call weused previously. This means that code that does not use CDI but does have access to the REST container SecurityContext can gethold of the caller JsonWebToken interface by casting the SecurityContext#getUserPrincipal().

The JsonWebToken interface defines methods for accessing claims in the underlying JWT. It provides accessors for commonclaims that are required by the MicroProfile JWT RBAC specification as well as arbitrary claims that may exist in the JWT.

Let’s expand our TokenSecuredResource with another endpoint /secured/winners. The winners() method, some hypothetical lottery winning number generator, whose code is shown in the following list:

TokenSecuredResource#winners Method Addition

  1. package org.acme.jwt;
  2. import java.security.Principal;
  3. import java.time.LocalDate;
  4. import java.util.ArrayList;
  5. import javax.annotation.security.PermitAll;
  6. import javax.annotation.security.RolesAllowed;
  7. import javax.inject.Inject;
  8. import javax.json.JsonString;
  9. import javax.ws.rs.GET;
  10. import javax.ws.rs.Path;
  11. import javax.ws.rs.Produces;
  12. import javax.ws.rs.core.Context;
  13. import javax.ws.rs.core.MediaType;
  14. import javax.ws.rs.core.SecurityContext;
  15. import org.eclipse.microprofile.jwt.Claims;
  16. import org.eclipse.microprofile.jwt.JsonWebToken;
  17. /**
  18. * Version 3 of the TokenSecuredResource
  19. */
  20. @Path("/secured")
  21. @RequestScoped
  22. public class TokenSecuredResourceV3 {
  23. @Inject
  24. JsonWebToken jwt;
  25. ...
  26. @GET
  27. @Path("winners")
  28. @Produces(MediaType.TEXT_PLAIN)
  29. @RolesAllowed("Subscriber")
  30. public String winners() {
  31. int remaining = 6;
  32. ArrayList<Integer> numbers = new ArrayList<>();
  33. // If the JWT contains a birthdate claim, use the day of the month as a pick
  34. if (jwt.containsClaim(Claims.birthdate.name())) { (1)
  35. String bdayString = jwt.getClaim(Claims.birthdate.name()); (2)
  36. LocalDate bday = LocalDate.parse(bdayString);
  37. numbers.add(bday.getDayOfMonth()); (3)
  38. remaining --;
  39. }
  40. // Fill remaining picks with random numbers
  41. while(remaining > 0) { (4)
  42. int pick = (int) Math.rint(64 * Math.random() + 1);
  43. numbers.add(pick);
  44. remaining --;
  45. }
  46. return numbers.toString();
  47. }
  48. }
1Here we use the injected JsonWebToken to check for a birthday claim.
2If it exists, we obtain the claim value as a String, and then convert it to a LocalDate.
3The day of month value of the birthday claim is inserted as the first winning number pick.
4The remainder of the winning number picks are random numbers.

This illustrates how you can use the JWT to not only provide identity and role based authorization, but as a stateless containerof information associated with the authenticated caller that can be used to alter you business method logic.Add this winners method to your TokenSecuredResource code, and run the following command, replacing YOUR_TOKEN witha new JWT or a long lived JWT you generated previously:

curl command for /secured/winners

  1. curl -H "Authorization: Bearer YOUR_TOKEN" http://localhost:8080/secured/winners; echo

Example output using my generated token is shown in the following example output. Note that the first pick corresponds to the day of month ofthe birthdate claim from the JwtClaims.json content.

Example Output for /secured/winners

  1. $ curl -H "Authorization: Bearer eyJraWQiOiJcL3ByaXZhdGVLZXkucGVtIiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiJqZG9lLXVzaW5nLWp3dC1yYmFjIiwiYXVkIjoidXNpbmctand0LXJiYWMiLCJ1cG4iOiJqZG9lQHF1YXJrdXMuaW8iLCJiaXJ0aGRhdGUiOiIyMDAxLTA3LTEzIiwiYXV0aF90aW1lIjoxNTUxNjY2MDMzLCJpc3MiOiJodHRwczpcL1wvcXVhcmt1cy5pb1wvdXNpbmctand0LXJiYWMiLCJyb2xlTWFwcGluZ3MiOnsiZ3JvdXAyIjoiR3JvdXAyTWFwcGVkUm9sZSIsImdyb3VwMSI6Ikdyb3VwMU1hcHBlZFJvbGUifSwiZ3JvdXBzIjpbIkVjaG9lciIsIlRlc3RlciIsIlN1YnNjcmliZXIiLCJncm91cDIiXSwicHJlZmVycmVkX3VzZXJuYW1lIjoiamRvZSIsImV4cCI6MTU1MTY2NjMzMywiaWF0IjoxNTUxNjY2MDMzLCJqdGkiOiJhLTEyMyJ9.LqJ5LlCrVIbCcRAus4aNHv7UfvuUgrcEVOwBxwfPY4c-YCaUxK0owxbtP2WtR5__yTFXpdplR6gVJWwv4Hw8c_sP8MRQi_5bdnTqZt3TeJsepx0cm7AIwJCopmpbuNjIgLVLZ_6VP3ZkZ2VK9SDO-9yBMPWWp2bnLILdwfYsOuJbFB_bWxSQYnTioms7NZjVefVY8eqawwfRq75PhB7W2iw-Ni2puVFjnpTiAZeCUCur-zjQ50QG6zSCZpVqPcI5JZ2-KeJKheiglYCYp0cauTdVgXjdlXCGQbAU0xirLxJXNsxg2GZxgV9luGwy1y3BdezwoM2m4mXviuHJP-lziA" http://localhost:8080/secured/winners; echo
  2. [13, 47, 42, 45, 19, 25]

Claims Injection

In the previous winners() method we accessed the birthday claim through the JsonWebToken interface. MicroProfile JWT RBAC also supportsthe direct injection of claim values from the JWT using CDI injection and the MicroProfile JWT RBAC @Claim qualifier. Here is an alternativeversion of the winners() method that injects the birthday claim value as an Optional<JsonString>:

TokenSecuredResource#winners2 Method Addition

  1. package org.acme.jwt;
  2. import java.security.Principal;
  3. import java.time.LocalDate;
  4. import java.util.ArrayList;
  5. import java.util.Optional;
  6. import javax.annotation.security.PermitAll;
  7. import javax.annotation.security.RolesAllowed;
  8. import javax.inject.Inject;
  9. import javax.ws.rs.GET;
  10. import javax.ws.rs.Path;
  11. import javax.ws.rs.Produces;
  12. import javax.ws.rs.core.Context;
  13. import javax.ws.rs.core.MediaType;
  14. import javax.ws.rs.core.SecurityContext;
  15. import org.eclipse.microprofile.jwt.Claim;
  16. import org.eclipse.microprofile.jwt.Claims;
  17. import org.eclipse.microprofile.jwt.JsonWebToken;
  18. /**
  19. * Version 4 of the TokenSecuredResource
  20. */
  21. @Path("/secured")
  22. @RequestScoped
  23. public class TokenSecuredResource {
  24. @Inject
  25. JsonWebToken jwt;
  26. @Inject (1)
  27. @Claim(standard = Claims.birthdate) (2)
  28. Optional<String> birthdate; (3)
  29. ...
  30. @GET
  31. @Path("winners2")
  32. @Produces(MediaType.TEXT_PLAIN)
  33. @RolesAllowed("Subscriber")
  34. public String winners2() {
  35. int remaining = 6;
  36. ArrayList<Integer> numbers = new ArrayList<>();
  37. // If the JWT contains a birthdate claim, use the day of the month as a pick
  38. if (birthdate.isPresent()) { (4)
  39. String bdayString = birthdate.get(); (5)
  40. LocalDate bday = LocalDate.parse(bdayString);
  41. numbers.add(bday.getDayOfMonth());
  42. remaining --;
  43. }
  44. // Fill remaining picks with random numbers
  45. while(remaining > 0) {
  46. int pick = (int) Math.rint(64 * Math.random() + 1);
  47. numbers.add(pick);
  48. remaining --;
  49. }
  50. return numbers.toString();
  51. }
  52. }
1We use CDI @Inject along with…​
2an MicroProfile JWT RBAC @Claim(standard = Claims.birthdate) qualifier to inject the birthdate claim directly as
3an Optional<String> value.
4Now we check whether the injected birthdate field is present
5and if it is, get its value.

The remainder of the code is the same as before. Update your TokenSecuredResource to either add or replace the currentwinners() method, and then invoke the following command with YOUR_TOKEN replaced:

curl command for /secured/winners2

  1. curl -H "Authorization: Bearer YOUR_TOKEN" http://localhost:8080/secured/winners2; echo

Example Output for /secured/winners2

  1. $ curl -H "Authorization: Bearer eyJraWQiOiJcL3ByaXZhdGVLZXkucGVtIiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiJqZG9lLXVzaW5nLWp3dC1yYmFjIiwiYXVkIjoidXNpbmctand0LXJiYWMiLCJ1cG4iOiJqZG9lQHF1YXJrdXMuaW8iLCJiaXJ0aGRhdGUiOiIyMDAxLTA3LTEzIiwiYXV0aF90aW1lIjoxNTUxNjY3MzEzLCJpc3MiOiJodHRwczpcL1wvcXVhcmt1cy5pb1wvdXNpbmctand0LXJiYWMiLCJyb2xlTWFwcGluZ3MiOnsiZ3JvdXAyIjoiR3JvdXAyTWFwcGVkUm9sZSIsImdyb3VwMSI6Ikdyb3VwMU1hcHBlZFJvbGUifSwiZ3JvdXBzIjpbIkVjaG9lciIsIlRlc3RlciIsIlN1YnNjcmliZXIiLCJncm91cDIiXSwicHJlZmVycmVkX3VzZXJuYW1lIjoiamRvZSIsImV4cCI6MTU1MTY3MDkxMywiaWF0IjoxNTUxNjY3MzEzLCJqdGkiOiJhLTEyMyJ9.c2QJAK3a1VOYL6vOt40VSEAy9wXPBEjVbqApTTNG8V8UDkQZ6HiOR9-rKOFX3WmTtQVru3O9zDu2_T2_v8kTmCkT-ThxodqC4VxD_QVx1v6BaSJ9-MX1Q7nrkD0Mk1V6x0Cqd6jmHxtJy0Ep8IgeMw2Y5gL9a1NgWVeldXP6cdHrHcYKYGnZKmYp7VpqZBoONPIS_QmWXm-JerwVpwt0juEtZUQoGCJdp7-GZA31QyEN64gCMKfdhYNnLuWQaom3i0uF_LfXtlMHdRU0kzDnLrnGw99ynTAex7ah7zG10ZbanK-PI-nD6wcTbE9WqriwohHM9BFJoBmF81RRk5uMsw" http://localhost:8080/secured/winners2; echo
  2. [13, 38, 36, 38, 36, 22]

Package and run the application

As usual, the application can be packaged using ./mvnw clean package and executed using the -runner.jar file:.Runner jar Example

  1. Scotts-iMacPro:security-jwt-quickstart starksm$ ./mvnw clean package
  2. [INFO] Scanning for projects...
  3. ...
  4. [INFO] [io.quarkus.creator.phase.runnerjar.RunnerJarPhase] Building jar: /Users/starksm/Dev/JBoss/Protean/starksm64-quarkus-quickstarts/security-jwt-quickstart/target/security-jwt-quickstart-runner.jar
  5. Scotts-iMacPro:security-jwt-quickstart starksm$ java -jar target/security-jwt-quickstart-runner.jar
  6. 2019-03-28 14:27:48,839 INFO [io.quarkus] (main) Quarkus 0.12.0 started in 0.796s. Listening on: http://[::]:8080
  7. 2019-03-28 14:27:48,841 INFO [io.quarkus] (main) Installed features: [cdi, resteasy, resteasy-jsonb, security, smallrye-jwt]

You can also generate the native executable with ./mvnw clean package -Pnative..Native Executable Example

  1. Scotts-iMacPro:security-jwt-quickstart starksm$ ./mvnw clean package -Pnative
  2. [INFO] Scanning for projects...
  3. ...
  4. [security-jwt-quickstart-runner:25602] universe: 493.17 ms
  5. [security-jwt-quickstart-runner:25602] (parse): 660.41 ms
  6. [security-jwt-quickstart-runner:25602] (inline): 1,431.10 ms
  7. [security-jwt-quickstart-runner:25602] (compile): 7,301.78 ms
  8. [security-jwt-quickstart-runner:25602] compile: 10,542.16 ms
  9. [security-jwt-quickstart-runner:25602] image: 2,797.62 ms
  10. [security-jwt-quickstart-runner:25602] write: 988.24 ms
  11. [security-jwt-quickstart-runner:25602] [total]: 43,778.16 ms
  12. [INFO] ------------------------------------------------------------------------
  13. [INFO] BUILD SUCCESS
  14. [INFO] ------------------------------------------------------------------------
  15. [INFO] Total time: 51.500 s
  16. [INFO] Finished at: 2019-03-28T14:30:56-07:00
  17. [INFO] ------------------------------------------------------------------------
  18. Scotts-iMacPro:security-jwt-quickstart starksm$ ./target/security-jwt-quickstart-runner
  19. 2019-03-28 14:31:37,315 INFO [io.quarkus] (main) Quarkus 0.12.0 started in 0.006s. Listening on: http://[::]:8080
  20. 2019-03-28 14:31:37,316 INFO [io.quarkus] (main) Installed features: [cdi, resteasy, resteasy-jsonb, security, smallrye-jwt]

Explore the Solution

The solution repository located in the security-jwt-quickstart directory contains all of the versions we haveworked through in this quickstart guide as well as some additional endpoints that illustrate subresources with injectionof JsonWebTokens and their claims into those using the CDI APIs. We suggest that you check out the quickstart solutions andexplore the security-jwt-quickstart directory to learn more about the Smallrye JWT extension features.

Configuration Reference

Quarkus configuration

Configuration property fixed at build time - ️ Configuration property overridable at runtime

Configuration propertyTypeDefault
quarkus.smallrye-jwt.enabledThe MP-JWT configuration objectbooleantrue
quarkus.smallrye-jwt.rsa-sig-providerThe name of the java.security.Provider that supports SHA256withRSA signaturesstringSunRsaSign

MicroProfile JWT configuration

Property NameDefaultDescription
mp.jwt.verify.publickeynoneThe mp.jwt.verify.publickey config property allows the Public Key text itself to be supplied as a string. The Public Key will be parsed from the supplied string in the order defined in section Supported Public Key Formats.
mp.jwt.verify.publickey.locationnoneConfig property allows for an external or internal location of Public Key to be specified. The value may be a relative path or a URL. If the value points to an HTTPS based JWK set then, for it to work in native mode, the quarkus.ssl.native property must also be set to true, see Using SSL With Native Executables for more details.
mp.jwt.verify.issuernoneConfig property specifies the value of the iss (issuer) claim of the JWT that the server will accept as valid.

Supported Public Key Formats

Public Keys may be formatted in any of the following formats, specified in order ofprecedence:

  • Public Key Cryptography Standards #8 (PKCS#8) PEM

  • JSON Web Key (JWK)

  • JSON Web Key Set (JWKS)

  • JSON Web Key (JWK) Base64 URL encoded

  • JSON Web Key Set (JWKS) Base64 URL encoded