| <?php |
| /** |
| * Copyright (c) 2020 Eclipse Foundation. |
| * |
| * This program and the accompanying materials are made |
| * available under the terms of the Eclipse Public License 2.0 |
| * which is available at https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * Contributors: |
| * Martin Lowe (Eclipse Foundation) - Initial implementation |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| */ |
| // This file must be included |
| if (basename(__FILE__) == basename($_SERVER['PHP_SELF'])) { |
| exit(); |
| } |
| ?> |
| <h1 class="article-title"><?php echo $pageTitle; ?></h1> |
| <p>In this article, I’ll describe a practical way to secure your Jakarta RESTful Web Services |
| (JAX-RS) APIs with Eclipse MicroProfile JavaScript Object Notation (JSON) Web Token (JWT). The |
| description here is based on a project I created in GitHub, which is linked at the end of the |
| article.</p> |
| <h2>Technical Context and Project Structure</h2> |
| <p>The project uses the following technologies:</p> |
| <ul> |
| <li>OpenJDK Runtime Environment AdoptOpenJDK (build 11.0.7+10)</li> |
| <li>MicroProfile 3.3</li> |
| <li>GraalVM 19.3.1 (for Quarkus native)</li> |
| <li>Wildfly 20.0.0.Final</li> |
| <li>Quarkus 1.5.0.Final</li> |
| <li>OpenLiberty 20.0.0.7</li> |
| <li>JWTenizr 0.0.4-SNAPSHOT</li> |
| </ul> |
| <p>The project is comprised of three independent modules, each of which is dedicated to a runtime: |
| Quarkus, Wildfly, or Liberty.</p> |
| <p>To keep it simple and ensure the code and configuration can be flexibly adapted, there is |
| deliberately nothing in common between these modules. As the saying goes, the devil is in the |
| details. Even when using a standard API such as MicroProfile JWT, there are often subtle |
| differences when it comes to configuration, coding, testing, and dependencies. However, I’ve |
| tried to keep the three modules as similar as possible.</p> |
| <p>Each module is comprised of a JAX-RS resource class, named GreetingResource, that exposes a |
| (very) simple REST API. The API has four methods, each of which returns a string value:</p> |
| <ul> |
| <li>hello: permitted to everybody</li> |
| <li>securedHello: restricted to users with the duke role</li> |
| <li>forbidden: restricted to users with the root role</li> |
| <li>getMyClaim: return the value of a custom claim</li> |
| </ul> |
| <p>The API is documented using OpenAPI and exposed with swagger-ui.</p> |
| <h2>A Closer Look at MicroProfile JWT</h2> |
| <p>The MicroProfile JWT programming model is fairly straightforward. It’s based on annotations |
| (@DenyAll, @PermitAll, @RolesAllowed) and uses Contexts and Dependency Injection (CDI) to inject |
| data coming from the token. It’s worth mentioning that JAX-RS resource classes must be |
| @RequestScoped, rather than @ApplicationScoped, to allow that injection for each request.</p> |
| <p>A test class named GreetingResourceTest, which is based on RestAssured, acts as a REST client to |
| enable testing of the different scenarios. It uses Arquillian for WildlFly and Open Liberty so |
| there’s no need for Arquillian with Quarkus.</p> |
| <p>MicroProfile JWT allows you to secure JAX-RS APIs in a scalable and stateless way, using a token |
| for each HTTP request. Each token is self-contained: It includes authorization and authentication |
| data as well as a signature to check its integrity and authenticity.</p> |
| <p>There are two key things to remember:</p> |
| <ul> |
| <li>A token is signed by an issuer using its private key.</li> |
| <li>The signature can be checked by third parties using the issuer’s public key.</li> |
| </ul> |
| <h2>Anatomy of a MicroProfile JWT Token</h2> |
| <p>A MicroProfile JWT token is comprised of three parts: |
| <header>.<body>.<signature></p> |
| <p>The body is composed of claims. A claim is a <key,value> pair. Some claims are standard, |
| but custom claims can be defined to transport additional data.</p> |
| <p>MicroProfile JWT introduces two specific claims:</p> |
| <ul> |
| <li><strong>upn</strong> (User Principal Name): Uniquely identifies the subject, or upn, of the |
| token. On the server side, this information can be retrieved as the name property of the |
| Principal and the JsonWebToken.</li> |
| <li><strong>groups</strong>: The subject’s group memberships that will be mapped to roles on |
| the server side. Typically, secured methods in JAX-RS class resources are annotated with |
| @RolesAllowed.</li> |
| </ul> |
| <h2>Using JWTenizr</h2> |
| <p>JWTenizr is an open source library created by Adam Bien. It generates a JWT token and a |
| MicroProfile configuration based on two input files:</p> |
| <ul> |
| <li>A configuration file, named <strong>jwtenizr-config.json</strong>, that defines the key pair |
| (public/private), the token issuer, and the location to generate microprofile-config.properties. |
| </li> |
| <li>A template file, named <strong>jwt-token.json</strong>, that defines the content of the token |
| body. |
| </li> |
| </ul> |
| <p>Figure 1 shows the relationships among these files.</p> |
| <h2>Figure 1: JWTenizr Input and Output Files</h2> |
| <p><img src="images/2_1.png" alt="Figure 1: JWTenizr Input and Output Files"></p> |
| <p>A token has a limited lifespan — 15 minutes with JWTenizr. To avoid token expiration during |
| tests, JWTenizr is called at the beginning of JUnit tests to generate a fresh token. As a result, |
| JWTenizr is defined in pom.xml as a test dependency.</p> |
| <p>Because JWTenizr is not available in the Maven Central Repository, you need to install it in your |
| local Maven repository by:</p> |
| <ol> |
| <li>Downloading JWTenizr from<a href="https://github.com/AdamBien/jwtenizr"> </a><a |
| href="https://github.com/AdamBien/jwtenizr" |
| >GitHub</a>. |
| </li> |
| <li>Running <code>mvn install</code>.</li> |
| </ol> |
| <h2>jwtenizr-config.json File Contents</h2> |
| <p>The code below show the contents of jwtenizr-config.json, the main configuration file:</p> |
| <code>{<br> |
| "mpConfigIssuer": "jefrajames",<br> |
| |
| "mpConfigurationFolder": "src/main/resources/META-INF",<br> |
| |
| "privateKey": "private key value here",<br> |
| |
| "publicKey": "public key value here"<br> |
| |
| }</code> |
| <p>Note: For Quarkus and Open Liberty, mpConfigurationFolder can’t be directly generated in |
| src/main/resources/META-INF.</p> |
| <h2>jwt-token.json File Contents</h2> |
| <p>The jwt-token.json template file defines the content of the body token in the form of claims, as |
| shown in the example below:</p> |
| <code>{"iss":"jefrajames","jti":"42","sub":"jf","upn":"james","groups":["chief","hacker","duke"],"myclaim":"customValue"}</code> |
| <p>In this example, four claims are particularly relevant:</p> |
| <ul> |
| <li>iss: Defines the token issuer. This value can optionally be controlled by the endpoint.</li> |
| <li>upn: Defines the User Principal Name.</li> |
| <li>groups: Defines the groups and roles the user belongs to.</li> |
| <li>myclaim: A custom claim.</li> |
| </ul> |
| <h2>Testing With curl</h2> |
| <p>To facilitate the use of curl, each project has a specific curl.sh script that uses the |
| last-generated token from token.jwt and targets the application-specific URL.</p> |
| <p>When run without arguments, curl.sh calls the default hello endpoint. Just add an argument to |
| call other endpoints. For example:</p> |
| <ul> |
| <li>curl.sh secured</li> |
| <li>curl.sh forbidden</li> |
| <li>curl.sh myclaims</li> |
| </ul> |
| <h2>Understanding the Impact on Performance</h2> |
| <p>Using Microprofile JWT can impact performance in multiple ways:</p> |
| <ul> |
| <li>It increases the size of HTTP requests. According to my tests, the size of a token is around |
| 600 bytes.</li> |
| <li>On the server side, it requires JAX-RS resource classes to be @RequestScoped rather than |
| @ApplicationScoped. This means these classes are not reusable and a new instance is created for |
| each request which adds some overhead.</li> |
| <li>The signature is checked for each request to validate the token.</li> |
| </ul> |
| <p>In most cases, the performance loss is acceptable, but should be kept in mind. Don’t be |
| surprised if your measurements indicate a performance reduction.</p> |
| <h2>Improving Security</h2> |
| <p>Each part of a JWT token is Base64-encoded. This encoding doesn’t mean the token is |
| ciphered. A “man in the middle” attack can steal and reuse it. This risk can be |
| mitigated in two ways:</p> |
| <ul> |
| <li>By limiting the token lifespan. In this case, a tradeoff must be made between performance and |
| security. Here’s a simple explanation: Small values increase security by limiting the risk |
| of inappropriate reuse, while high values increase performance because fewer tokens are |
| generated.</li> |
| <li>By using HTTPS as the transport layer. With this approach, a ciphered communication channel is |
| established between clients and servers, preventing tokens from being stolen and reused.</li> |
| </ul> |
| <p>Needless to say, in production, both mitigations are recommended.</p> |
| <p>Two additional security measures are also required:</p> |
| <ul> |
| <li><strong>Using a Public Key Infrastructure (PKI)</strong>. MicroProfile JWT is based on RSA |
| algorithms that use public/private key pairs. Public key distribution and renewal must be taken |
| into account using a PKI.</li> |
| <li><strong>Using Identity and Access Management (IAM). </strong>Using an IAM such as Keycloak in |
| production is a must.</li> |
| </ul> |
| <h2>Check Out the Project in GitHub</h2> |
| <p>Developing this project allowed me to see how easy it is to secure a JAX-RS API using |
| MicroProfile JWT from a developer perspective.</p> |
| <p>There are some differences between Open Liberty, WildFly and Quarkus — primarily in terms |
| of configuration and testing — but the majority of the code remains the same. I’ve |
| described these differences in README files that are included in the project.</p> |
| <p> |
| To learn more, check out the project in<a href="https://github.com/jefrajames/jwt-lab"> </a><a |
| href="https://github.com/jefrajames/jwt-lab" |
| >GitHub</a>. |
| </p> |
| <h2>Get More Information</h2> |
| <p>For additional insight into the technologies mentioned in this article, see:</p> |
| <ul> |
| <li><a href="https://www.tomitribe.com/blog/microprofile-json-web-token-jwt/">MicroProfile JSON |
| Web Token</a> by Jean-Louis Monteiro from <a href="https://www.tomitribe.com/">Tomitribe</a>.</li> |
| <li><a href="https://www.adam-bien.com/roller/abien/entry/json_web_token_generator_jwtenizr">Securing |
| JAX-RS Endpoint with JWT</a>, a video by Adam Bien that demonstrates how to use JWTenizr with |
| Quarkus.</li> |
| <li><a |
| href="http://www.mastertheboss.com/javaee/eclipse-microservices/using-jwt-role-based-access-control-with-wildfly" |
| >Using JWT Role Based Access Control with WildFly</a>, published on mastertheboss.com.</li> |
| <li><a |
| href="https://suedbroecker.net/2020/06/30/getting-started-to-secure-a-java-microservice-with-keycloak-microprofile-and-openliberty/" |
| >Getting Started to Secure a Simple Java Microservice With Keycloak, MicroProfile and Open Liberty</a> |
| by Thomas Suedbroecker from IBM.</li> |
| </ul> |
| <div class="margin-bottom-20"> |
| <?php print $Theme->getShareButtonsHTML(); ?> |
| </div> |
| <div class="bottomitem margin-bottom-20"> |
| <h3>About the Author</h3> |
| <div class="row"> |
| <div class="col-sm-24"> |
| <div class="row margin-bottom-20"> |
| <div class="col-sm-8"> |
| <img class="img-responsive" alt="<?php print $pageAuthor; ?>" src="images/jean_francois.jpg" /> |
| </div> |
| <div class="col-sm-16"> |
| <p class="author-name"><?php print $pageAuthor; ?></p> |
| <p class="author-bio">Jean-François James is a senior software architect, Worldline distinguished expert, open source contributor, blogger, speaker, father of three, and martial artist.</p> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |