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 token-based authentication mechanisms to authenticate, authorize,
and verify users by implementing MicroProfile JWT in the system
microservice.
A JSON Web Token (JWT) is a self-contained token that is designed to securely transmit information as a JSON object. The information in this JSON object is digitally signed and can be trusted and verified by the recipient.
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 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 claims to various Jakarta EE container APIs and make the set of claims available through getter methods.
In this guide, the application uses JWTs to authenticate a user, allowing them to make authorized requests to a secure backend service.
You will be working with two services, a frontend
service and a secure
system
backend service. The frontend
service logs a user in, builds a JWT, and makes
authorized requests to the secure system
service for JVM system properties.
The following diagram depicts the application that is used in this guide:
The user signs in to the frontend
service with a username and a password,
at which point a JWT is created. The frontend
service then makes
requests, with the JWT included, to the system
backend service. The secure
system
service verifies the JWT to ensure that the request came
from the authorized frontend
service. After the JWT is validated, the information
in the claims, such as the user’s role, can be trusted and used to
determine which system properties the user has access to.
To learn more about JSON Web Tokens, check out the jwt.io website. If you want to learn more about how JWTs can be used for user authentication and authorization, check out the Open Liberty Single Sign-on documentation.
The finish
directory contains the finished JWT security implementation for the
services in the application. Try the finished application before you
build your own.
To try out the application, run the following commands to navigate to the finish/frontend
directory and
deploy the frontend
service to Open Liberty:
cd finish/frontend
mvn liberty:run
Open another command-line session and run the following commands to navigate to the finish/system
directory and
deploy the system
service to Open Liberty:
cd finish/system
mvn liberty:run
After you see the following message in both command-line sessions, both of your services are ready:
The defaultServer server is ready to run a smarter planet.
In your browser, go to the front-end web application endpoint at http://localhost:9090/login.jsf. From here, you can log in to the application with the form-based login.
Log in with one of the following usernames and its corresponding password:
Username |
Password |
Role |
bob |
bobpwd |
admin, user |
alice |
alicepwd |
user |
carl |
carlpwd |
user |
You’re redirected to a page that displays information that the
front end requested from the system
service, such as the system username.
If you log in as an admin
user, the current OS is also shown. If you log in as
a user
, you instead see the You are not authorized to access
this system property
message. You see this message because the user
role doesn’t
have sufficient privileges to view current OS information.
Additionally, the groups
claim of the JWT is read by the system
service and
requested by the front end to be displayed.
You can try accessing these services without a JWT by going to the
https://localhost:8443/system/properties/os
system
endpoint in your browser. You get a blank screen and aren’t given
access because you didn’t supply a valid JWT with the request. The following
error also appears in the command-line session of the system
service:
[ERROR] CWWKS5522E: The MicroProfile JWT feature cannot perform authentication because a MicroProfile JWT cannot be found in the request.
When you are done with the application, stop both the frontend
and system
services by pressing CTRL+C
in the command-line sessions where you ran them.
Alternatively, you can run the following goals from the finish
directory in
another command-line session:
mvn -pl system liberty:stop
mvn -pl frontend liberty:stop
Navigate to the start
directory to begin.
When you run Open Liberty in development mode, known as dev mode, the server
listens for file changes and automatically recompiles and deploys your updates
whenever you save a new change. Run the following commands to navigate to the
frontend
directory and start the frontend
service in dev mode:
cd frontend
mvn liberty:dev
Open another command-line session and run the following commands to navigate to the
system
directory and start the system
service in dev mode:
cd system
mvn liberty:dev
After you see the following message, your application server in dev mode is ready:
************************************************************************
* Liberty is running in dev mode.
The system
service provides endpoints for the frontend
service to use to
request system properties. This service is secure and requires a valid JWT to be
included in requests that are made to it. The claims in the JWT are used to determine
what properties the user has access to.
Create the secure system
service.
SystemResource.java
link:finish/system/src/main/java/io/openliberty/guides/system/SystemResource.java[role=include]
Create theSystemResource
class.system/src/main/java/io/openliberty/guides/system/SystemResource.java
This class has role-based access control. The role names that are used in the
@RolesAllowed
annotations 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 /username
endpoint returns the system’s username and is annotated with the
@RolesAllowed({"admin, "user"})
annotation. Only authenticated users with the role of admin
or user
can access this endpoint.
The /os
endpoint returns the system’s current OS. Here,
the @RolesAllowed
annotation is limited to
admin
, meaning that only authenticated users with the role of admin
are able to
access the endpoint.
While the @RolesAllowed
annotation automatically reads from the groups
claim
of the JWT to make an authorization decision, you can also manually access the
claims of the JWT by using the @Claim
annotation. In this
case, the groups
claim is injected into the roles
JSON array. The roles that are parsed from the groups
claim of the
JWT are then exposed back to the front end at the /jwtroles
endpoint. To read more about different claims and ways to
access them, check out the
MicroProfile JWT
documentation.
SystemClient.java
link:finish/frontend/src/main/java/io/openliberty/guides/frontend/client/SystemClient.java[role=include]
ApplicationBean.java
link:finish/frontend/src/main/java/io/openliberty/guides/frontend/ApplicationBean.java[role=include]
LoginBean.java
link:finish/frontend/src/main/java/io/openliberty/guides/frontend/LoginBean.java[role=include]
Create a RESTful client interface for the frontend
service.
Create theSystemClient
class.frontend/src/main/java/io/openliberty/guides/frontend/client/SystemClient.java
This interface declares methods for accessing each of the endpoints that were
previously set up in the system
service.
The MicroProfile Rest Client feature automatically builds and generates a client
implementation based on what is defined in the SystemClient
interface. You
don’t need to set up the client and connect with the remote service.
As discussed, the system
service is secured and requests made to it must
include a valid JWT in the Authorization
header. The @HeaderParam
annotations
include the JWT by specifying that the value of the String authHeader
parameter, which contains the JWT, be used as the value for the Authorization
header. This header is included in all of the requests that are made to the
system
service through this client.
Create the application bean that the front-end UI uses to request data.
Create theApplicationBean
class.frontend/src/main/java/io/openliberty/guides/frontend/ApplicationBean.java
The application bean is used to populate the table in the front end by making
requests for data through the defaultRestClient
, which
is an injected instance of the SystemClient
class
that you created. The getOs()
, getUsername()
, and getJwtRoles()
methods call
their associated methods of the SystemClient
class
with the authHeader
passed in as a parameter. The authHeader
is a string that
consists of the JWT with Bearer
prefixed to it. The authHeader
is included in the
Authorization
header of the subsequent requests that are made by the
defaultRestClient
instance.
The JWT for these requests is retrieved from the session attributes with the
getJwt()
method. The JWT is stored in the session
attributes by the provided LoginBean
class. When the
user logs in to the front end, the doLogin()
method is
called and builds the JWT. Then, the setAttribute()
method stores it as an HttpSession
attribute. The JWT is built by using the
JwtBuilder
APIs in the buildJwt()
method. You can see that the claim()
method is being used to set the groups
claim of the token.
This claim is used to provide the role-based access that you implemented.
microprofile-config.properties
link:finish/system/src/main/webapp/META-INF/microprofile-config.properties[role=include]
server.xml
link:finish/system/src/main/liberty/config/server.xml[role=include]
Configure the mpJwt
feature in the microprofile-config.properties
file for the system
service.
Create the microprofile-config.properties file.
system/src/main/webapp/META-INF/microprofile-config.properties
The mp.jwt.verify.issuer
config property specifies the expected value of
the issuer claim on an incoming JWT. Incoming JWTs with an issuer
claim that’s different from this expected value aren’t considered valid.
Next, add the MicroProfile JSON Web Token feature to the server configuration file for
the system
service.
Replace the system server configuration file.
system/src/main/liberty/config/server.xml
The mpJwt
feature adds the libraries that are required for MicroProfile JWT implementation.
Because you are running the frontend
and system
services in dev mode, the changes that you made were automatically picked up. You’re now ready to check out your application in your browser.
In your browser, go to the front-end web application endpoint at http://localhost:9090/login.jsf. Log in with one of the following usernames and its corresponding password:
Username |
Password |
Role |
bob |
bobpwd |
admin, user |
alice |
alicepwd |
user |
carl |
carlpwd |
user |
After you log in, you can see the information that’s retrieved from the system
service. With successfully implemented role-based access in the application, if
you log in as a user
role, you don’t have access to the OS property.
You can also see the value of the groups
claim in the row with the Roles:
label.
These roles are read from the JWT and sent back to the front end to
be displayed.
You can check that the system
service is secured against unauthenticated
requests by going to the https://localhost:8443/system/properties/os
system
endpoint in your browser.
In the front end, you see your JWT displayed in the row with the JSON Web Token
label.
To see the specific information that this JWT holds, you can enter it into the token reader on the JWT.io website. The token reader shows you the header, which contains information about the JWT, as shown in the following example:
{
"kid": "NPzyG3ZMzljUwQgbzi44",
"typ": "JWT",
"alg": "RS256"
}
The token reader also shows you the payload, which contains the claims information:
{
"token_type": "Bearer",
"sub": "bob",
"upn": "bob",
"groups": [ "admin", "user" ],
"iss": "http://openliberty.io",
"exp": 1596723489,
"iat": 1596637089
}
You can learn more about these claims in the MicroProfile JWT documentation.
SystemEndpointIT.java
link:finish/system/src/test/java/it/io/openliberty/guides/system/SystemEndpointIT.java[role=include]
You can manually check that the system
service is secure by making requests to
each of the endpoints with and without valid JWTs. However, automated tests are a
much better approach because they are more reliable and trigger a failure if a
breaking change is introduced.
Create theSystemEndpointIT
class.system/src/test/java/it/io/openliberty/guides/system/SystemEndpointIT.java
The testOSEndpoint()
, testUsernameEndpoint()
, and testRolesEndpoint()
tests test the /os
, /username
, and /roles
endpoints.
Each test makes three requests to its associated endpoint. The first
makeRequest()
call has a JWT with the admin
role. The second
makeRequest()
call has a JWT with the user
role. The third
makeRequest()
call has no JWT at all. The responses to these
requests are checked based on the role-based access rules for the endpoints. The
admin
requests should be successful on all endpoints. The user
requests
should be denied by the /os
endpoint but successfully access the /username
and /jwtroles
endpoints. The requests that don’t include a JWT should be
denied access to all endpoints.
Because you started Open Liberty in dev mode, press the enter/return
key from the
command-line session of the system
service to run the tests. You see the
following output:
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO] Running it.io.openliberty.guides.system.SystemEndpointIT
[INFO] [ERROR ] CWWKS5522E: The MicroProfile JWT feature cannot perform authentication because a MicroProfile JWT cannot be found in the request.
[INFO] [ERROR ] CWWKS5522E: The MicroProfile JWT feature cannot perform authentication because a MicroProfile JWT cannot be found in the request.
[INFO] [ERROR ] CWWKS5522E: The MicroProfile JWT feature cannot perform authentication because a MicroProfile JWT cannot be found in the request.
[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 2.648 s - in it.io.openliberty.guides.system.SystemEndpointIT
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0
The three errors in the output are expected and result from the system
service successfully
rejecting the requests that didn’t include a JWT.
When you are finished testing the application, stop both the frontend
and system
services by pressing CTRL+C
in the command-line sessions where you ran them.
Alternatively, you can run the following goals from the start
directory in
another command-line session:
mvn -pl system liberty:stop
mvn -pl frontend liberty:stop
You learned how to use MicroProfile JWT to validate JWTs, authenticate and authorize users to secure your microservices in Open Liberty.