blob: 4c7e82c55623704c873efed7f046fe8b0340833f [file] [log] [blame]
<?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&rsquo;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&rsquo;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&rsquo;s based on annotations
(@DenyAll, @PermitAll, @RolesAllowed) and uses Contexts and Dependency Injection (CDI) to inject
data coming from the token. It&rsquo;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&rsquo;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&rsquo;s public key.</li>
</ul>
<h2>Anatomy of a MicroProfile JWT Token</h2>
<p>A MicroProfile JWT token is comprised of three parts:
&lt;header&gt;.&lt;body&gt;.&lt;signature&gt;</p>
<p>The body is composed of claims. A claim is a &lt;key,value&gt; 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&rsquo;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 &mdash; 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>
&nbsp; &quot;mpConfigIssuer&quot;: &quot;jefrajames&quot;,<br>
&nbsp; &quot;mpConfigurationFolder&quot;: &quot;src/main/resources/META-INF&quot;,<br>
&nbsp; &quot;privateKey&quot;: &quot;private key value here&quot;,<br>
&nbsp; &quot;publicKey&quot;: &quot;public key value here&quot;<br>
}</code>
<p>Note: For Quarkus and Open Liberty, mpConfigurationFolder can&rsquo;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>{&quot;iss&quot;:&quot;jefrajames&quot;,&quot;jti&quot;:&quot;42&quot;,&quot;sub&quot;:&quot;jf&quot;,&quot;upn&quot;:&quot;james&quot;,&quot;groups&quot;:[&quot;chief&quot;,&quot;hacker&quot;,&quot;duke&quot;],&quot;myclaim&quot;:&quot;customValue&quot;}</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&rsquo;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&rsquo;t mean the token is
ciphered. A &ldquo;man in the middle&rdquo; 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&rsquo;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 &mdash; primarily in terms
of configuration and testing &mdash; but the majority of the code remains the same. I&rsquo;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>