Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/openliberty/sample-keycloak
Sample app using Open Liberty, Jakarta EE Security, MicroProfile JWT, and Keycloak.
https://github.com/openliberty/sample-keycloak
jakartaee jwt keycloak microprofile oidc openliberty
Last synced: about 2 months ago
JSON representation
Sample app using Open Liberty, Jakarta EE Security, MicroProfile JWT, and Keycloak.
- Host: GitHub
- URL: https://github.com/openliberty/sample-keycloak
- Owner: OpenLiberty
- License: epl-2.0
- Created: 2023-03-22T16:26:51.000Z (almost 2 years ago)
- Default Branch: main
- Last Pushed: 2023-11-06T21:11:25.000Z (over 1 year ago)
- Last Synced: 2024-11-06T05:15:56.213Z (3 months ago)
- Topics: jakartaee, jwt, keycloak, microprofile, oidc, openliberty
- Language: Java
- Homepage:
- Size: 96.7 KB
- Stars: 2
- Watchers: 4
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Sample App using Jakarta EE Security, MicroProfile JWT, and Keycloak
This sample app contains a `system` service which has a `GET /api/system/properties/username` endpoint, which returns the `user.name` system property, and a `GET /api/system/properties/os` endpoint, which returns the `os.name` system property. The endpoints are secured with JWT's using the `MicroProfile JWT 2.0` feature. Only users with the `admin` role can access the `GET /api/system/properties/username` endpoint, while users with either the `admin` or `user` role can access the `GET /api/system/properties/os` endpoint. The sample app also contains a `gateway` service whose responsibility is to obtain a JWT access token for the user from `Keycloak` and propagate it to the `system` service. This is accomplished using the `Jakarta EE Security 3.0` feature.
## Starting the application
### Start Keycloak
Run from the root directory:
```
docker build -t openliberty-keycloak -f keycloak/Dockerfile .
docker run --name openliberty-keycloak -p 8080:8080 -d openliberty-keycloak
```### Start the System service
Run from the `src/system` directory:
```
mvn liberty:dev
```### Start the Gateway service
In another shell instance, run from the `src/gateway` directory:
```
mvn liberty:dev
```## Testing the application
### Users
| Username | Password | Role |
| ---------- | ---------- | ----------- |
| alice | alicepwd | user |
| bob | bobpwd | admin, user |### Testing the username endpoint
Visit the username endpoint at the http://localhost:9090/api/system/properties/username URL. Log in as `bob` to view the `user.name` system property. Logging in as `alice` will result in a `403 Forbidden` response because only users with the `admin` role are allowed to access this endpoint.
Log out of the current user by visiting the logout endpoint at the http://localhost:9090/api/auth/logout URL.
### Testing the os endpoint
Visit the os endpoint at the http://localhost:9090/api/system/properties/os URL. Log in as `bob` or `alice` to view the `os.name` system property.
Log out of the current user by visiting the logout endpoint at the http://localhost:9090/api/auth/logout URL.
## How it works
![Token propagation diagram](/assets/diagram.png)
### Keycloak
Keycloak was used as the OpenId Connect Provider (OP) for this sample app. The `openliberty` realm was created in Keycloak. Inside this realm, the `sample-openliberty-keycloak` client was created which has the valid redirect URI's set to `http://localhost:9090/*`, client authentication enabled, and the `microprofile-jwt` client scope enabled. Additionally, the `admin` and `user` roles were created for this realm. Lastly, the `alice` user was created which has the `user` role and the `bob` user was created which has the `admin` role and the `user` role. Visit the http://localhost:8080/admin/master/console/#/openliberty URL and sign in using the `admin` username and `admin` password to view the `openliberty` realm.
The Keycloak OP's configuration can found at the http://localhost:8080/realms/openliberty/.well-known/openid-configuration URL.
### System Service
The `system` service secures its endpoints using MicroProfile JWT. By adding the `@RolesAllowed` annotation to a JAX-RS endpoint, the runtime will check `Authentication` HTTP request header for a Bearer token. The Bearer token must be in JWT format and include the specified role in the annotation in the JWT's `roles` claim in order to access the endpoint.
For the `/username` endpoint, it requires that the Bearer token's `roles` claim include the `admin` role.
[SystemResource.java](https://github.com/OpenLiberty/sample-keycloak/blob/main/src/system/src/main/java/io/openliberty/guides/system/SystemResource.java#L21-L26)
```java
@GET
@Path("/username")
@RolesAllowed({ "admin" })
public String getUsername() {
return System.getProperties().getProperty("user.name");
}
```The following lines in the `microprofile-config.properties` file are required for MicroProfile JWT to validate that the Bearer token was issued from the correct source and has not been tampered with.
[microprofile-config.properties](https://github.com/OpenLiberty/sample-keycloak/blob/main/src/system/src/main/webapp/META-INF/microprofile-config.properties#L1-L2)
```properties
mp.jwt.verify.issuer=http://localhost:8080/realms/openliberty
mp.jwt.verify.publickey.location=http://localhost:8080/realms/openliberty/protocol/openid-connect/certs
```The `mp.jwt.verify.issuer` should match the `issuer` value in the Keycloak OP config. The runtime will validate that the Bearer token's `iss` claim matches the `mp.jwt.verify.issuer` value. The `mp.jwt.verify.publickey.location` should match the `jwks_uri` value in the Keycloak OP config. The runtime will validate the Bearer token's signature with the public key found at the Keycloak OP's JSON Web Key (JWK) Set endpoint.
### Gateway Service
The `gateway` service is used to obtain a JWT access token from Keycloak and propagates it to the MicroProfile JWT secured `system` service.
For the `gateway` service, an `@OpenIdAuthenticationMechanismDefinition` is defined which has its `providerURI` set to the Keycloak OP's configuration URL, the `clientID` set to the `sample-openliberty-keycloak` client, and the `clientSecret` set to `sample-openliberty-keycloak`'s client secret. The `redirectToOriginalResource` is set to true, so that the browser will redirect to the original resource request after authentication. The `notifyProvider` is set to true, so that the browser will also redirect to the Keycloak OP's `end_session_endpoint` in the event of a logout. The `@DeclareRoles` annotation declares the `admin` role and the `user` role.
[AuthResource.java](https://github.com/OpenLiberty/sample-keycloak/blob/main/src/gateway/src/main/java/io/openliberty/guides/gateway/auth/AuthResource.java#L27-L33)
```java
@OpenIdAuthenticationMechanismDefinition(
providerURI = "http://localhost:8080/realms/openliberty/.well-known/openid-configuration",
clientId = "sample-openliberty-keycloak",
clientSecret = "x4fRVAhk49TKDqVlzIt4q9oh8DSWfePt",
redirectToOriginalResource = true,
logout = @LogoutDefinition(notifyProvider = true))
@DeclareRoles({ "admin", "user" })
```Now, adding the `@RolesAllowed` annotation to a JAX-RS endpoint will start a new OpenId Connect (OIDC) authorization code flow. The roles specified in the annotation for the `/username` endpoint in the `gateway` service should match the roles for the `/username` endpoint in the `system` service. If successful, an access token is obtained from the `OpenIdContext` and is propagated to the `system` service as a Bearer token in the `Authorization` header via MicroProfile Rest Client.
[SystemResource.java](https://github.com/OpenLiberty/sample-keycloak/blob/main/src/gateway/src/main/java/io/openliberty/guides/gateway/system/SystemResource.java#L32-L37)
```java
@GET
@Path("/username")
@RolesAllowed({ "admin" })
public String getUsername() {
return systemService.getUsername(openIdContext.getAccessToken().getToken());
}
```