Securing microservices with JSON Web Tokens

duration 25 minutes
Git clone to get going right away:
git clone https://github.com/OpenLiberty/guide-microprofile-jwt.git
Copy Github clone command

You’ll explore how to control user and role access to microservices with MicroProfile JSON Web Token (MicroProfile JWT).

What you’ll learn

You will add MicroProfile JWT to validate security tokens in the system and inventory microservices. You will use a token-based authentication mechanism to authenticate, authorize, and verify user identities based on a security token.

In addition, you will learn how to verify token claims through getters with MicroProfile JWT.

For microservices, a token-based authentication mechanism offers a lightweight way for security controls and security tokens to propagate user identities across different services. JSON Web Token (JWT) is becoming the most common token format because it follows well-defined and known standards.

MicroProfile JWT standards define the required format of JWT for authentication and authorization. The standards also map JWT token claims to various Java EE container APIs and make the set of claims available through getters.

The application that you will be working with is an inventory service, which stores the information about various JVMs that run on different systems. Whenever a request is made to the inventory service to retrieve the JVM system properties of a particular host, the inventory service communicates with the system service on that host to get these system properties. The JWT token gets propagated and verified during the communication between two services.

Getting started

The fastest way to work through this guide is to clone the Git repository and use the projects that are provided inside:

git clone https://github.com/openliberty/guide-microprofile-jwt.git
cd guide-microprofile-jwt

The start directory contains the starting project that you will build upon.

The finish directory contains the finished project that you will build.

Try what you’ll build

The finish directory contains the finished JWT security implementation for the services in the application. You can try the finished application before you build your own.

To try out the application, first navigate to the finish directory. Next, execute the following Maven goals to build the application and run it inside of Open Liberty:

mvn install liberty:start-server

Note that you cannot directly visit a back end URL from a browser. You have to use the provided front end that generates valid JWT tokens to retrieve information from the back end.

Navigate your browser to the front end web application endpoint: http://localhost:9090/application.jsf. From here, you can log in to the application with the form-based login. Log in with one of the following user information credentials:

Username

Password

Role

bob

bobpwd

admin, user

alice

alicepwd

user

carl

carlpwd

user

You are redirected to a page that displays the user who is logged in, the current OS of your localhost, and the security token that the front end uses when it sends the HTTP request to access the data from the back end. In addition, if you log in as an admin user, the current inventory size appears. If you log in as a user, you instead see the message, You are not authorized to access the inventory service, which means you cannot access the inventory service in the back end.

When you are done with the application, stop the server with the following command:

mvn liberty:stop-server

Securing back end services with MicroProfile JWT

pom.xml

  1<?xml version='1.0' encoding='utf-8'?>
  2<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  3    <modelVersion>4.0.0</modelVersion>
  4
  5    <parent>
  6        <groupId>io.openliberty.guides</groupId>
  7        <artifactId>microprofile-jwt</artifactId>
  8        <version>0.0.1-SNAPSHOT</version>
  9    </parent>
 10
 11    <artifactId>backendServices</artifactId>
 12    <packaging>war</packaging>
 13
 14    <properties>
 15        <warfile.name>backendMicroservice</warfile.name>
 16        <server.name>backendServer</server.name>
 17    </properties>
 18
 19    <dependencies>
 20        <!-- Open Liberty features -->
 21        <dependency>
 22            <groupId>io.openliberty.features</groupId>
 23            <!-- tag::mpJwt[] -->
 24            <artifactId>mpJwt-1.1</artifactId>
 25            <!-- end::mpJwt[] -->
 26            <type>esa</type>
 27        </dependency>
 28        <dependency>
 29            <groupId>io.openliberty.features</groupId>
 30            <artifactId>ssl-1.0</artifactId>
 31            <type>esa</type>
 32        </dependency>
 33        <dependency>
 34            <groupId>io.openliberty.features</groupId>
 35            <artifactId>jaxrs-2.1</artifactId>
 36            <type>esa</type>
 37        </dependency>
 38        <dependency>
 39            <groupId>io.openliberty.features</groupId>
 40            <artifactId>jsonp-1.1</artifactId>
 41            <type>esa</type>
 42        </dependency>
 43        <dependency>
 44            <groupId>io.openliberty.features</groupId>
 45            <artifactId>cdi-2.0</artifactId>
 46            <type>esa</type>
 47        </dependency>
 48        <dependency>
 49            <groupId>io.openliberty.features</groupId>
 50            <artifactId>appSecurity-3.0</artifactId>
 51            <type>esa</type>
 52        </dependency>
 53        <!-- For testing -->
 54        <dependency>
 55            <groupId>io.openliberty.guides</groupId>
 56            <artifactId>resources</artifactId>
 57            <version>${version.shared.keystore}</version>
 58            <scope>test</scope>
 59        </dependency>
 60        <dependency>
 61            <groupId>org.glassfish</groupId>
 62            <artifactId>javax.json</artifactId>
 63        </dependency>
 64        <dependency>
 65            <groupId>org.apache.cxf</groupId>
 66            <artifactId>cxf-rt-rs-client</artifactId>
 67        </dependency>
 68        <dependency>
 69            <groupId>junit</groupId>
 70            <artifactId>junit</artifactId>
 71        </dependency>
 72        <!-- The following is necessary to read CXF client configuration -->
 73        <dependency>
 74            <groupId>org.springframework</groupId>
 75            <artifactId>spring-context</artifactId>
 76        </dependency>
 77        <!-- Support for JDK 9 and above -->
 78        <dependency>
 79            <groupId>javax.xml.bind</groupId>
 80            <artifactId>jaxb-api</artifactId>
 81        </dependency>
 82        <dependency>
 83            <groupId>com.sun.xml.bind</groupId>
 84            <artifactId>jaxb-core</artifactId>
 85        </dependency>
 86        <dependency>
 87            <groupId>com.sun.xml.bind</groupId>
 88            <artifactId>jaxb-impl</artifactId>
 89        </dependency>
 90        <dependency>
 91            <groupId>javax.activation</groupId>
 92            <artifactId>activation</artifactId>
 93        </dependency>
 94    </dependencies>
 95
 96    <build>
 97        <plugins>
 98            <plugin>
 99                <artifactId>maven-resources-plugin</artifactId>
100                <executions>
101                      <!-- Copy the backend application to the liberty server -->
102                    <execution>
103                        <id>copy-app-to-liberty</id>
104                        <phase>package</phase>
105                        <goals>
106                            <goal>copy-resources</goal>
107                        </goals>
108                        <configuration>
109                            <outputDirectory>${project.build.directory}/liberty/wlp/usr/servers/${server.name}/apps</outputDirectory>
110                            <overwrite>true</overwrite>
111                            <resources>
112                                <resource>
113                                    <directory>${project.build.directory}</directory>
114                                    <includes>
115                                        <include>${warfile.name}.war</include>
116                                    </includes>
117                                </resource>
118                            </resources>
119                        </configuration>
120                    </execution>
121                    <!-- Copy the keystore to integration tests for HTTPS -->
122                    <execution>
123                        <id>copy-keystore-to-integration-test</id>
124                        <phase>pre-integration-test</phase>
125                        <goals>
126                            <goal>copy-resources</goal>
127                        </goals>
128                        <configuration>
129                            <outputDirectory>${basedir}/target/test-classes/truststore</outputDirectory>
130                            <overwrite>true</overwrite>
131                            <resources>
132                                <resource>
133                                    <directory>${project.build.directory}/liberty/wlp/usr/servers/${server.name}/resources/security</directory>
134                                    <includes>
135                                        <include>keystore.jceks</include>
136                                    </includes>
137                                </resource>
138                            </resources>
139                        </configuration>
140                    </execution>
141                </executions>
142            </plugin>
143            <plugin>
144                <groupId>org.apache.maven.plugins</groupId>
145                <artifactId>maven-dependency-plugin</artifactId>
146                <executions>
147                    <!-- Copy the keystore that Liberty will use -->
148                    <execution>
149                        <id>copy-keystore-to-liberty</id>
150                        <phase>package</phase>
151                        <goals>
152                            <goal>unpack</goal>
153                        </goals>
154                        <configuration>
155                            <artifactItems>
156                                <artifact>
157                                    <groupId>io.openliberty.guides</groupId>
158                                    <artifactId>resources</artifactId>
159                                    <type>jar</type>
160                                    <overWrite>false</overWrite>
161                                    <outputDirectory>${project.build.directory}/liberty/wlp/usr/servers/${server.name}/resources/security</outputDirectory>
162                                    <includes>keystore.jceks</includes>
163                                </artifact>
164                            </artifactItems>
165                        </configuration>
166                    </execution>
167                </executions>
168            </plugin>
169
170            <!-- Liberty setup. -->
171            <plugin>
172                <groupId>net.wasdev.wlp.maven.plugins</groupId>
173                <artifactId>liberty-maven-plugin</artifactId>
174                <executions>
175                    <execution>
176                        <id>install-apps</id>
177                        <configuration>
178                            <appsDirectory>apps</appsDirectory>
179                            <stripVersion>true</stripVersion>
180                            <installAppPackages>project</installAppPackages>
181                            <looseApplication>true</looseApplication>
182                        </configuration>
183                    </execution>
184                </executions>
185                <configuration>
186                    <serverName>${server.name}</serverName>
187                    <appArchive>${project.build.directory}/${warfile.name}.war</appArchive>
188                    <configFile>${basedir}/src/main/liberty/config/server.xml</configFile>
189                    <bootstrapProperties>
190                        <app.name>${warfile.name}.war</app.name>
191                        <http.port>${backend.http.port}</http.port>
192                        <https.port>${backend.https.port}</https.port>
193                        <frontend.hostname>${frontend.hostname}</frontend.hostname>
194                        <frontend.port>${frontend.https.port}</frontend.port>
195                        <jwt.issuer>${jwt.issuer}</jwt.issuer>
196                    </bootstrapProperties>
197                </configuration>
198            </plugin>
199
200            <!-- Integration test execution setup. -->
201            <plugin>
202                <groupId>org.apache.maven.plugins</groupId>
203                <artifactId>maven-failsafe-plugin</artifactId>
204                <version>3.0.0-M1</version>
205                <executions>
206                    <execution>
207                        <phase>integration-test</phase>
208                        <id>integration-test</id>
209                        <goals>
210                            <goal>integration-test</goal>
211                        </goals>
212                        <configuration>
213                            <includes>
214                                <include>**/it/**/*.java</include>
215                            </includes>
216                            <systemPropertyVariables>
217                                <liberty.test.hostname>${backend.hostname}</liberty.test.hostname>
218                                <liberty.test.port>${backend.http.port}</liberty.test.port>
219                                <liberty.test.ssl.port>${backend.https.port}</liberty.test.ssl.port>
220                                <liberty.backend.service.hostname>${backend.hostname}</liberty.backend.service.hostname>
221                                <liberty.backend.service.ssl.port>${backend.https.port}</liberty.backend.service.ssl.port>
222                            </systemPropertyVariables>
223                        </configuration>
224                    </execution>
225                    <execution>
226                        <id>verify-results</id>
227                        <goals>
228                            <goal>verify</goal>
229                        </goals>
230                    </execution>
231                </executions>
232                <configuration>
233                    <summaryFile>${project.build.directory}/test-reports/microprofile-jwt/failsafe-summary.xml</summaryFile>
234                    <reportsDirectory>${project.build.directory}/test-reports/microprofile-jwt</reportsDirectory>
235                </configuration>
236            </plugin>
237
238            <!-- Dep to run integration tests. -->
239            <plugin>
240                <groupId>org.apache.maven.plugins</groupId>
241                <artifactId>maven-surefire-plugin</artifactId>
242                <version>3.0.0-M1</version>
243                <executions>
244                    <execution>
245                        <phase>test</phase>
246                        <id>default-test</id>
247                        <configuration>
248                            <excludes>
249                                <exclude>**/it/**</exclude>
250                            </excludes>
251                            <reportsDirectory>${project.build.directory}/test-reports/unit</reportsDirectory>
252                        </configuration>
253                    </execution>
254                </executions>
255            </plugin>
256        </plugins>
257    </build>
258</project>

Navigate to the start directory.

The MicroProfile JWT feature, mpJwt, has been added as a dependency in your pom.xml file.

Create the server configuration file.
backendServices/src/main/liberty/config/server.xml

server.xml

 1<!-- tag::copyright[] -->
 2<!-- Copyright (c) 2018, 2019 IBM Corporation and others.
 3    All rights reserved. This program and the accompanying materials
 4    are made available under the terms of the Eclipse Public License v1.0
 5    which accompanies this distribution, and is available at
 6    http://www.eclipse.org/legal/epl-v10.html
 7
 8    Contributors:
 9    IBM Corporation - initial API and implementation
10-->
11<!-- end::copyright[] -->
12<!-- tag::jwt[] -->
13<server description="Backend server">
14    <featureManager>
15        <!-- tag::mpJwt[] -->
16        <feature>mpJwt-1.1</feature>
17        <!-- end::mpJwt[] -->
18        <feature>ssl-1.0</feature>
19        <feature>jaxrs-2.1</feature>
20        <feature>jsonp-1.1</feature>
21        <feature>cdi-2.0</feature>
22        <feature>appSecurity-3.0</feature>
23    </featureManager>
24
25    <!-- This is the keystore that will be used by SSL and by JWT.
26         The keystore is built using the maven keytool plugin -->
27    <keyStore id="defaultKeyStore" location="keystore.jceks" type="JCEKS"
28              password="secret" />
29
30    <!-- The HTTP ports that the application will use. -->
31    <httpEndpoint id="defaultHttpEndpoint" host="*" httpPort="${http.port}"
32                  httpsPort="${https.port}"/>
33
34    <!-- The application containing the user and login endpoints. -->
35    <webApplication location="${app.name}" contextRoot="/" />
36
37    <!-- The MP JWT configuration that injects the caller's JWT into a
38         ResourceScoped bean for inspection. -->
39    <!-- tag::mpJwtTag[] -->
40    <mpJwt id="jwtUserConsumer" keyName="default" audiences="simpleapp"
41           issuer="${jwt.issuer}"/>
42    <!-- end::mpJwtTag[] -->
43</server>
44<!-- end::jwt[] -->

The mpJwt feature adds the libraries that are required for MicroProfile JWT implementation.

The <mpJwt/> element is required to configure the injection of the caller’s JWT.

keyName

Specifies the key alias name to locate the public key for JWT signature validation and must be in the keystore in the SSL configuration.

audiences

Identifies the recipients and must match the aud claim in the JWT.

issuer

Issues security tokens and must match the iss claim in the JWT.

Securing the system service

Update the SystemResource class.
backendServices/src/main/java/io/openliberty/guides/system/SystemResource.java

Add the @RolesAllowed annotation.

SystemResource.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2017, 2019 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License v1.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-v10.html
 8 *
 9 * Contributors:
10 *     IBM Corporation - Initial implementation
11 *******************************************************************************/
12// end::copyright[]
13// tag::jwt[]
14package io.openliberty.guides.system;
15
16import java.util.Properties;
17import javax.enterprise.context.RequestScoped;
18import javax.ws.rs.GET;
19import javax.ws.rs.Path;
20import javax.ws.rs.Produces;
21import javax.ws.rs.core.MediaType;
22import javax.annotation.security.RolesAllowed;
23
24@RequestScoped
25@Path("properties")
26public class SystemResource {
27  @GET
28  // tag::RolesAllowed[]
29  @RolesAllowed({ "admin", "user" })
30  // end::RolesAllowed[]
31  @Produces(MediaType.APPLICATION_JSON)
32  public Properties getProperties() {
33    return System.getProperties();
34  }
35}
36// end::jwt[]

You have just added roles-based access control to the SystemResource class. Role names that are used in the @RolesAllowed annotation are mapped to group names in the groups claim of the JWT, which results in an authorization decision wherever the security constraint is applied.

The @RolesAllowed({"admin", "user"}) annotation allows only authenticated users with the role of admin or user to access the system service. Therefore, this service is properly secured.

Securing the inventory service

Update the InventoryResource class.
backendServices/src/main/java/io/openliberty/guides/inventory/InventoryResource.java

Add the @RolesAllowed annotations.

InventoryResource.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2018, 2019 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License v1.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-v10.html
 8 *
 9 * Contributors:
10 *     IBM Corporation - Initial implementation
11 *******************************************************************************/
12// end::copyright[]
13// tag::jwt[]
14package io.openliberty.guides.inventory;
15
16import java.util.Properties;
17import javax.enterprise.context.RequestScoped;
18import javax.inject.Inject;
19import javax.ws.rs.GET;
20import javax.ws.rs.Path;
21import javax.ws.rs.PathParam;
22import javax.ws.rs.Produces;
23import javax.ws.rs.core.MediaType;
24import javax.ws.rs.core.Response;
25import io.openliberty.guides.inventory.model.InventoryList;
26import javax.annotation.security.RolesAllowed;
27import javax.ws.rs.core.HttpHeaders;
28import javax.ws.rs.core.Context;
29
30@RequestScoped
31@Path("systems")
32public class InventoryResource {
33
34  @Inject
35  InventoryManager manager;
36
37  @GET
38  // tag::RolesAllowed[]
39  @RolesAllowed({ "admin", "user" })
40  // end::RolesAllowed[]
41  @Path("{hostname}")
42  @Produces(MediaType.APPLICATION_JSON)
43  // tag::getPropertiesForHost[]
44  public Response getPropertiesForHost(@PathParam("hostname") String hostname,
45      @Context HttpHeaders httpHeaders) {
46    String authHeader = httpHeaders.getRequestHeaders()
47                                   .getFirst(HttpHeaders.AUTHORIZATION);
48    // Get properties
49    // tag::managerGet[]
50    Properties props = manager.get(hostname, authHeader);
51    // end::managerGet[]
52    if (props == null) {
53      return Response.status(Response.Status.NOT_FOUND)
54                     .entity(
55                         "ERROR: Unknown hostname or the resource"
56                         + "may not be running on the host machine")
57
58                     .build();
59    }
60
61    // Add to inventory
62    manager.add(hostname, props);
63    return Response.ok(props).build();
64  }
65  // end::getPropertiesForHost[]
66
67  @GET
68  // tag::RolesAllowed2[]
69  @RolesAllowed({ "admin" })
70  // end::RolesAllowed2[]
71  @Produces(MediaType.APPLICATION_JSON)
72  public InventoryList listContents() {
73    return manager.list();
74  }
75}
76// end::jwt[]

You have just added roles-based access control to the InventoryResource class. Similarly, for each specific service, the @RolesAllowed annotation sets which roles are allowed to access it. Therefore, the inventory service is properly secured, as well.

In addition, the getPropertiesForHost() method gets the HTTP Authorization header, which contains the corresponding security token, from the HTTP request caller. The method uses this authorization header to retrieve information from the system service.

The final addition of the manager.get(hostname, authHeader) code adds the authentication header to the client that sends the HTTP GET request to the system service.

Adding the SecureSystemClient class

In the beginning, the inventory service uses the normal SystemClient class to create a client and send HTTP GET requests to retrieve information from the system service. However, after you secure the system service with token-based authentication, you need to add security tokens when you send HTTP requests through the client.

Create the SecureSystemClient class.
backendServices/src/main/java/io/openliberty/guides/inventory/client/SecureSystemClient.java

SecureSystemClient.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2018, 2019 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License v1.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-v10.html
 8 *
 9 * Contributors:
10 *     IBM Corporation - Initial implementation
11 *******************************************************************************/
12// end::copyright[]
13// tag::jwt[]
14package io.openliberty.guides.inventory.client;
15
16import javax.enterprise.context.RequestScoped;
17import javax.ws.rs.client.Invocation.Builder;
18import javax.ws.rs.core.HttpHeaders;
19import java.util.Properties;
20import io.openliberty.guides.inventory.client.SystemClient;
21
22@RequestScoped
23// tag::SecureSystemClient[]
24// tag::SystemClient[]
25public class SecureSystemClient extends SystemClient {
26// end::SystemClient[]
27
28  // Constants for building URI to the system service.
29  private final int DEFAULT_SEC_PORT = Integer.valueOf(
30      System.getProperty("https.port"));
31  private final String SYSTEM_PROPERTIES = "/system/properties";
32  private final String SECURED_PROTOCOL = "https";
33
34  public String buildUrl(String protocol, String host, int port, String path) {
35    return super.buildUrl(protocol, host, port, path);
36  }
37
38  // tag::buildClientBuilder[]
39  public Builder buildClientBuilder(String url, String authHeader) {
40    Builder builder = super.buildClientBuilder(url);
41    // tag::return[]
42    return builder.header(HttpHeaders.AUTHORIZATION, authHeader);
43    // end::return[]
44  }
45  // end::buildClientBuilder[]
46
47  public Properties getProperties(String hostname, String authHeader) {
48    String url = buildUrl(SECURED_PROTOCOL, hostname,
49                          DEFAULT_SEC_PORT, SYSTEM_PROPERTIES);
50    Builder clientBuilder = buildClientBuilder(url, authHeader);
51    return getPropertiesHelper(clientBuilder);
52  }
53}
54// end::SecureSystemClient[]
55// end::jwt[]

The SecureSystemClient class inherits methods from the SystemClient class. These methods hand the HTTP GET request to the system service to retrieve system properties. However, the SecureSystemClient class overrides the buildClientBuilder() method by adding the authorization header to the returned builder, return builder.header(HttpHeaders.AUTHORIZATION, authHeader). By adding the authorization header to the client request, the SecureSystemClient class can access services that are secured.

Trying more JAX-RS methods to validate tokens

To demonstrate how MicroProfile JWT retrieves confidential information and validates custom claims from the security token, add an additional service that uses JAX-RS security-related methods. The JWT service also illustrates how JAX-RS methods map to MicroProfile JWT features.

Create the JwtResource class.
backendServices/src/main/java/io/openliberty/guides/inventory/JwtResource.java

JwtResource.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2018, 2019 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License v1.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-v10.html
 8 *
 9 * Contributors:
10 *     IBM Corporation - Initial implementation
11 *******************************************************************************/
12// end::copyright[]
13// tag::jwt[]
14package io.openliberty.guides.inventory;
15
16import java.util.Set;
17import javax.enterprise.context.RequestScoped;
18import javax.inject.Inject;
19import javax.ws.rs.GET;
20import javax.ws.rs.Path;
21import javax.ws.rs.core.Response;
22import javax.ws.rs.core.Context;
23// tag::JsonWebTokenImport[]
24import org.eclipse.microprofile.jwt.JsonWebToken;
25// end::JsonWebTokenImport[]
26import javax.annotation.security.RolesAllowed;
27import javax.ws.rs.core.SecurityContext;
28// tag::javaSecurity[]
29import java.security.Principal;
30// end::javaSecurity[]
31
32@RequestScoped
33@Path("jwt")
34public class JwtResource {
35  // The JWT of the current caller. Since this is a request scoped resource, the
36  // JWT will be injected for each JAX-RS request. The injection is performed by
37  // the mpJwt-1.0 feature.
38  @Inject
39  // tag::JsonWebToken[]
40  private JsonWebToken jwtPrincipal;
41  // end::JsonWebToken[]
42
43  @GET
44  @RolesAllowed({ "admin", "user" })
45  @Path("/username")
46  // tag::getJwtUsername[]
47  public Response getJwtUsername() {
48    // tag::getName[]
49    return Response.ok(this.jwtPrincipal.getName()).build();
50    // end::getName[]
51  }
52  // end::getJwtUsername[]
53
54  @GET
55  @RolesAllowed({ "admin", "user" })
56  @Path("/groups")
57  // tag::getJwtGroups[]
58  public Response getJwtGroups(@Context SecurityContext securityContext) {
59    Set<String> groups = null;
60    // tag::securityContext[]
61    Principal user = securityContext.getUserPrincipal();
62    // end::securityContext[]
63    if (user instanceof JsonWebToken) {
64      JsonWebToken jwt = (JsonWebToken) user;
65      // tag::groups[]
66      groups = jwt.getGroups();
67      // end::groups[]
68    }
69    return Response.ok(groups.toString()).build();
70  }
71  // end::getJwtGroups[]
72
73  @GET
74  @RolesAllowed({ "admin", "user" })
75  @Path("/customClaim")
76  // tag::getCustomClaim[]
77  public Response getCustomClaim(@Context SecurityContext securityContext) {
78    // tag::isUserInRole[]
79    if (securityContext.isUserInRole("admin")) {
80    // end::isUserInRole[]
81      // tag::customClaim[]
82      String customClaim = jwtPrincipal.getClaim("customClaim");
83      // end::customClaim[]
84      return Response.ok(customClaim).build();
85    }
86    // tag::responseStatus[]
87    return Response.status(Response.Status.FORBIDDEN).build();
88    // end::responseStatus[]
89  }
90  // end::getCustomClaim[]
91}
92// end::jwt[]

MicroProfile maps the JWT claims to JAX-RS security-related methods and makes the set of claims available through getters.

The getJwtUsername() function retrieves the user name from the injected JsonWebToken jwtPrincipal token by calling the getName() method.

The getJwtGroups() function retrieves the user groups information from the JSON Web Token. The SecurityContext.getUserPrincipal() method returns an object of the java.security.Principal type. This object is also an instance of the org.eclipse.microprofile.jwt.JsonWebToken type, which has the getGroups() method.

The getCustomClaim() function retrieves the custom claim from the token if the authenticated user has the admin role. Otherwise, it returns a Status.FORBIDDEN message. The SecurityContext.isUserInRole(String) method returns a true value for any role that appears in the MicroProfile JWT groups claim because role names are mapped to group names in the claim. The jwtPrincipal.getClaim("customClaim") method retrieves the value of the custom claim by its name. This method is useful when you want to validate information about a custom claim from the security token.

Building and running the application

To build the application, run the Maven install phase from the command line in the start directory:

mvn install

This command builds the application and creates a .war file in the target directory. It also configures and installs Open Liberty into the target/liberty/wlp directory.

Next, run the Maven liberty:start-server goal:

mvn liberty:start-server

This goal starts an Open Liberty server instance. Your Maven pom.xml is already configured to start the application in this server instance.

After the Open Liberty application server starts, you can log in to the application with the simple front end. The entire front end code is provided for you to test if you can retrieve information from the back end services by using a valid security token.

You cannot directly visit a back end URL from a browser because no valid tokens exist in the authorization header when you send HTTP requests through a browser. You have to use the provided front end to create a web client to send HTTP GET requests with valid security tokens.

Use one of the following credentials to log in:

Username

Password

Role

bob

bobpwd

admin, user

alice

alicepwd

user

carl

carlpwd

user

Use the following endpoint to log in to the application:

Once you log in, you can see some basic information retrieved from the back end services. Remember that if you log in as a user without the admin role, you cannot see the inventory size because you do not have permission.

If you make changes to the code, use the Maven compile goal to rebuild the application and have the running Open Liberty server pick them up automatically:

mvn compile

To stop the Open Liberty server, run the Maven liberty:stop-server goal:

mvn liberty:stop-server

Testing the application

Write SystemEndpointTest, InventoryEndpointTest, and JwtTest classes to test that you can access different services with valid tokens.

Create the SystemEndpointTest class.
backendServices/src/test/java/it/io/openliberty/guides/jwt/SystemEndpointTest.java

SystemEndpointTest.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2017, 2019 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License v1.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-v10.html
 8 *
 9 * Contributors:
10 *     IBM Corporation - Initial implementation
11 *******************************************************************************/
12// end::copyright[]
13// tag::test[]
14package it.io.openliberty.guides.jwt;
15
16import static org.junit.Assert.assertEquals;
17
18import javax.json.JsonObject;
19import javax.ws.rs.core.Response;
20import javax.ws.rs.core.Response.Status;
21import org.junit.Before;
22import org.junit.Test;
23import it.io.openliberty.guides.jwt.util.TestUtils;
24import it.io.openliberty.guides.jwt.util.JwtVerifier;
25
26public class SystemEndpointTest {
27
28  private final String SYSTEM_PROPERTIES = "/system/properties";
29  private final String TESTNAME = "TESTUSER";
30
31  String baseUrl = "https://" + System.getProperty("liberty.test.hostname") + ":"
32      + System.getProperty("liberty.test.ssl.port");
33
34  String authHeader;
35
36  @Before
37  // tag::setup[]
38  public void setup() throws Exception {
39    authHeader = "Bearer " + new JwtVerifier().createAdminJwt(TESTNAME);
40  }
41  // end::setup[]
42
43  @Test
44  public void testSuite() {
45    this.testGetPropertiesWithJwt();
46  }
47
48  public void testGetPropertiesWithJwt() {
49    // Get system properties by using Jwt token
50    String propUrl = baseUrl + SYSTEM_PROPERTIES;
51    Response propResponse = TestUtils.processRequest(propUrl, "GET", null,
52        authHeader);
53
54    assertEquals(
55        "HTTP response code should have been " + Status.OK.getStatusCode() + ".",
56        Status.OK.getStatusCode(), propResponse.getStatus());
57
58    JsonObject responseJson = TestUtils.toJsonObj(
59        propResponse.readEntity(String.class));
60
61    assertEquals("The system property for the local and remote JVM should match",
62        System.getProperty("os.name"), responseJson.getString("os.name"));
63  }
64}
65// end::test[]
Create the InventoryEndpointTest class.
backendServices/src/test/java/it/io/openliberty/guides/jwt/InventoryEndpointTest.java

InventoryEndpointTest.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2017, 2019 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License v1.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-v10.html
 8 *
 9 * Contributors:
10 *     IBM Corporation - Initial implementation
11 *******************************************************************************/
12// end::copyright[]
13// tag::test[]
14package it.io.openliberty.guides.jwt;
15
16import static org.junit.Assert.assertEquals;
17
18import javax.json.JsonObject;
19import javax.ws.rs.core.Response;
20import javax.ws.rs.core.Response.Status;
21import org.junit.Before;
22import org.junit.Test;
23import it.io.openliberty.guides.jwt.util.TestUtils;
24import it.io.openliberty.guides.jwt.util.JwtVerifier;
25
26public class InventoryEndpointTest {
27
28  private final String INVENTORY_HOSTS = "/inventory/systems";
29  private final String TESTNAME = "TESTUSER";
30
31  String baseUrl = "https://" + System.getProperty("liberty.test.hostname") + ":"
32      + System.getProperty("liberty.test.ssl.port");
33
34  String authHeader;
35
36  @Before
37  // tag::setup[]
38  public void setup() throws Exception {
39    authHeader = "Bearer " + new JwtVerifier().createAdminJwt(TESTNAME);
40  }
41  // end::setup[]
42
43  @Test
44  public void testSuite() {
45    this.testEmptyInventoryWithJwt();
46    this.testHostRegistrationWithJwt();
47  }
48
49  public void testEmptyInventoryWithJwt() {
50    String invUrl = baseUrl + INVENTORY_HOSTS;
51    Response invResponse = TestUtils.processRequest(invUrl, "GET", null, authHeader);
52
53    assertEquals(
54      // tag::statusOK[]
55        "HTTP response code should have been " + Status.OK.getStatusCode() + ".",
56        Status.OK.getStatusCode(), invResponse.getStatus());
57      // end::statusOK[]
58
59    JsonObject responseJson = TestUtils.toJsonObj(
60        invResponse.readEntity(String.class));
61
62    assertEquals("The inventory should be empty on application start", 0,
63        responseJson.getInt("total"));
64  }
65
66  public void testHostRegistrationWithJwt() {
67    String invUrl = baseUrl + INVENTORY_HOSTS + "/localhost";
68    Response invResponse = TestUtils.processRequest(invUrl, "GET", null, authHeader);
69
70    assertEquals(
71        "HTTP response code should have been " + Status.OK.getStatusCode() + ".",
72        Status.OK.getStatusCode(), invResponse.getStatus());
73
74    JsonObject responseJson = TestUtils.toJsonObj(
75        invResponse.readEntity(String.class));
76
77    assertEquals("The inventory should get the os.name of localhost",
78        System.getProperty("os.name"), responseJson.getString("os.name"));
79  }
80
81}
82// end::test[]

In the setup() step before test cases, an authorization header is created with the credentials of a test user who has the TESTUSER name and the admin role. This authorization header is added when the test client sends an HTTP GET request to a secure service to retrieve information.

Each test case tries to first assert that with a valid authorization header, it can get a Status.OK code from the response. The test fails if the response returns a Status.FORBIDDEN message, which means the authorization header is invalid.

After each test confirms that the response code is correct, each test gets the content from the designated endpoint and compares the result with its expected value.

Create the JwtTest class.
backendServices/src/test/java/it/io/openliberty/guides/jwt/JwtTest.java

JwtTest.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2018, 2019 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License v1.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-v10.html
 8 *
 9 * Contributors:
10 *     IBM Corporation - Initial implementation
11 *******************************************************************************/
12// end::copyright[]
13// tag::test[]
14package it.io.openliberty.guides.jwt;
15
16import static org.junit.Assert.assertEquals;
17
18import javax.ws.rs.core.Response;
19import javax.ws.rs.core.Response.Status;
20import org.junit.Test;
21import org.junit.Before;
22import it.io.openliberty.guides.jwt.util.TestUtils;
23import it.io.openliberty.guides.jwt.util.JwtVerifier;
24
25public class JwtTest {
26
27  private final String TESTNAME = "TESTUSER";
28  private final String INV_JWT = "/inventory/jwt";
29
30  String baseUrl = "https://" + System.getProperty("liberty.test.hostname") + ":"
31      + System.getProperty("liberty.test.ssl.port");
32
33  String authHeader;
34
35  @Before
36  // tag::setup[]
37  public void setup() throws Exception {
38    authHeader = "Bearer " + new JwtVerifier().createUserJwt(TESTNAME);
39  }
40  // end::setup[]
41
42  @Test
43  public void testSuite() {
44    this.testJwtGetName();
45    this.testJwtGetCustomClaim();
46  }
47
48  // tag::testJwtGetName[]
49  public void testJwtGetName() {
50    String jwtUrl = baseUrl + INV_JWT + "/username";
51    Response jwtResponse = TestUtils.processRequest(jwtUrl, "GET", null, authHeader);
52
53    assertEquals(
54        "HTTP response code should have been " + Status.OK.getStatusCode() + ".",
55        Status.OK.getStatusCode(), jwtResponse.getStatus());
56
57    String responseName = jwtResponse.readEntity(String.class);
58
59    assertEquals("The test name and jwt token name should match", TESTNAME,
60        responseName);
61  }
62  // end::testJwtGetName[]
63
64  // tag::testJwtGetCustomClaim[]
65  public void testJwtGetCustomClaim() {
66    String jwtUrl = baseUrl + INV_JWT + "/customClaim";
67    Response jwtResponse = TestUtils.processRequest(jwtUrl, "GET", null, authHeader);
68
69    assertEquals("HTTP response code should have been "
70        + Status.FORBIDDEN.getStatusCode() + ".", Status.FORBIDDEN.getStatusCode(),
71        jwtResponse.getStatus());
72  }
73  // end::testJwtGetCustomClaim[]
74
75}
76// end::test[]

In the setup() step of the JwtTest class, an authorization header is created with the credentials of a test user who has the TESTUSER name and the user role. The authorization header is added when the test client sends an HTTP GET request to a secure JWT service.

The testJWTGetName() test accesses the https://localhost:5051/inventory/jwt/username endpoint to get the username from the JWT token and to verify whether the username is the same as the TESTUSER user name.

The testJWTGetCustomClaim() test accesses the https://localhost:5051/inventory/jwt/customClaim endpoint. Because this service is secured and only accessible to users who have the admin role, the test asserts that the current test user with the user role is forbidden from accessing it.

Running the tests

If the server is still running from the previous steps, stop it using the Maven liberty:stop-server goal from command line in the start directory:

mvn liberty:stop-server

Then, verify that the tests pass using the Maven verify goal:

mvn verify

It may take some time before build is complete. If the tests pass, you will see a similar output to the following:

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running it.io.openliberty.guides.jwt.InventoryEndpointTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 2.434 sec - in it.io.openliberty.guides.jwt.InventoryEndpointTest
Running it.io.openliberty.guides.jwt.JwtTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.117 sec - in it.io.openliberty.guides.jwt.JwtTest
Running it.io.openliberty.guides.jwt.SystemEndpointTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.069 sec - in it.io.openliberty.guides.jwt.SystemEndpointTest

Results :

Tests run: 3, Failures: 0, Errors: 0, Skipped: 0

To see whether the tests detect a failure, remove the authorization header generation in the setup() method of the InventoryEndpointTest.java file. Rerun the Maven build. You see that a test failure occurs.

Great work! You’re done!

You learned how to use MicroProfile JWT to validate JWTs and authorize users to secure your microservices in Open Liberty.

Next, you can try one of the related MicroProfile guides. They demonstrate technologies that you can learn and expand on what you built here.

Guide Attribution

Securing microservices with JSON Web Tokens by Open Liberty is licensed under CC BY-ND 4.0

Copied to clipboard
Copy code block
Copy file contents
Git clone this repo to get going right away:
git clone https://github.com/OpenLiberty/guide-microprofile-jwt.git
Copy github clone command
Copied to clipboard

Nice work! Where to next?

What did you think of this guide?

Extreme Dislike Dislike Like Extreme Like

What could make this guide better?

Raise an issue to share feedback

Create a pull request to contribute to this guide

Need help?

Ask a question on Stack Overflow

Like Open Liberty? Star our repo on GitHub.

Star