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).
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.
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
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.
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.
|
Specifies the key alias name to locate the public key for JWT signature validation and must be in the keystore in the SSL configuration. |
|
Identifies the recipients and must match the |
|
Issues security tokens and must match the |
Update theSystemResource
class.backendServices/src/main/java/io/openliberty/guides/system/SystemResource.java
Add the @RolesAllowed
annotation.
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.
Update theInventoryResource
class.backendServices/src/main/java/io/openliberty/guides/inventory/InventoryResource.java
Add the @RolesAllowed
annotations.
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.
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 theSecureSystemClient
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.
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 theJwtResource
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.
Write SystemEndpointTest
, InventoryEndpointTest
, and JwtTest
classes to test that you can access different services with valid tokens.
Create theSystemEndpointTest
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 theInventoryEndpointTest
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 theJwtTest
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.
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.