justineechen / guide-microprofile-jwt

An introductory guide on how to control user and role access to microservices with MicroProfile JSON Web Token (MicroProfile JWT): https://openliberty.io/guides/microprofile-jwt.html

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Securing microservices with JSON Web Tokens

Note
This repository contains the guide documentation source. To view the guide in published form, view it on the Open Liberty website.

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.

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

link:finish/backendServices/pom.xml[role=include]

Navigate to the start directory.

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

Creating the server configuration file

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

server.xml

link:finish/backendServices/src/main/liberty/config/server.xml[role=include]

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

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

SystemResource.java

link:finish/backendServices/src/main/java/io/openliberty/guides/system/SystemResource.java[role=include]

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

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

InventoryResource.java

link:finish/backendServices/src/main/java/io/openliberty/guides/inventory/InventoryResource.java[role=include]

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

link:finish/backendServices/src/main/java/io/openliberty/guides/inventory/client/SecureSystemClient.java[role=include]

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

link:finish/backendServices/src/main/java/io/openliberty/guides/inventory/JwtResource.java[role=include]

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.

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.

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

link:finish/backendServices/src/test/java/it/io/openliberty/guides/jwt/SystemEndpointTest.java[role=include]
Create the InventoryEndpointTest class.
backendServices/src/test/java/it/io/openliberty/guides/jwt/InventoryEndpointTest.java

InventoryEndpointTest.java

link:finish/backendServices/src/test/java/it/io/openliberty/guides/jwt/InventoryEndpointTest.java[role=include]

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

link:finish/backendServices/src/test/java/it/io/openliberty/guides/jwt/JwtTest.java[role=include]

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.

-------------------------------------------------------
 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.

About

An introductory guide on how to control user and role access to microservices with MicroProfile JSON Web Token (MicroProfile JWT): https://openliberty.io/guides/microprofile-jwt.html

License:Other


Languages

Language:Java 84.1%Language:HTML 15.9%