Security using OpenID Connect
Securing endpoints using Bearer Token Authorization
You can protect your JAX-RS microservices by using Bearer Token Authorization where Bearer Tokens are issued by OpenId Connect and OAuth 2.0 compliant Authorization Servers such as Keycloak.
In this section we will secure our endpoints with Bearer Tokens by using Dev Services when running in Dev Mode and, lastly, by configuring the KeyCloak server for all the run modes.
Working with DevServices for KeyCloak
Quarkus introduced an experimental Dev Services For Keycloak feature which is enabled by default when the quarkus-oidc
extension is started in dev mode. It starts a Keycloak container and initializes it by registering the existing Keycloak realm or creating a new realm with the client and users for you to start developing your Quarkus application secured by Keycloak immediately.
When working in Dev Mode, you can use Dev Services for KeyCloak. This is the easy way to locally test your KeyCloak authentication.
Simply go to Dev UI and select the OpenId Connect Card linking to a Keycloak page.
Click on the Provider: Keycloak link and you will see a Keycloak page which will be presented slightly differently depending on how Dev Services for Keycloak feature has been configured.
By default, alice and bob users (with the passwords matching the names), and user and admin roles are created.
alice has both admin and user roles, and bob has just the user role.
|
However, we can add our own user, role and group by following these steps:
-
Click on KeyCloak Admin link in the left corner.
-
Login using `admin' as user and password.
-
Go to https://raw.githubusercontent.com/redhat-developer-demos/quarkus-tutorial/master/jwt-token/quarkus-realm.json and save it on your local machine.
-
Go to Import in the KeyCloak Administration console and import
quarkus-realm.json
. After this step you should haveSubscriber
Role and Group. -
Go to Users and check that
jdoe
user is mapped to theSubscriber
role and group. -
Setup a password for
jdoe
user.
Congratulations! Now you can run this section in Dev Mode without starting the KeyCloak docker container nor adding the KeyCloak setup in application.properties
.
Dev Services For Keycloak will not be activated if either If you would like to disable Dev Services For Keycloak, just add |
Create UserResource
If you need to access JsonWebToken
claims, you can simply inject the token itself.
Create the UserResource
Java class in the com.redhat.developers
package with the following contents:
package com.redhat.developers;
import javax.annotation.security.RolesAllowed;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import org.eclipse.microprofile.jwt.JsonWebToken;
@Path("api/users")
public class UserResource {
@Inject
JsonWebToken jwt;
@GET
@Path("info")
@RolesAllowed("Subscriber") (1)
public String getInfo() {
return "Access for subject " + jwt.getName() + " is granted";
}
}
1 | The endpoint is accessible only to users that have Subscriber role. |
Invoke the /api/users/info endpoint with RBAC
First you need a token valid to authenticate. Run the following command to obtain an access token:
curl -X POST 'http://localhost:56006/auth/realms/quarkus/protocol/openid-connect/token' \ (1)
-H "Content-Type: application/x-www-form-urlencoded" \
-d "username=jdoe" \
-d 'password=the_pass_you_set' \ (2)
-d 'grant_type=password' \
-d 'client_id=admin-cli'
1 | If you are using KeyCloak Dev Services, you can get the KeyCloak port from Quarkus logs. |
2 | Consider to replace the password here with the one you set in the beginning. |
You should see an output similar to:
{"access_token":"eyJhbGciOiJSUzI......","token_type":"Bearer","not-before-policy":0,"session_state":"84349a48-55ea-4c25-88cd-d26a775c8c67","scope":"email profile"}
You can store the access token in a variable and use it further for querying.
Below you can find details on how to do that using curl
and jq
:
token=$(curl -X POST 'http://localhost:56006/auth/realms/quarkus/protocol/openid-connect/token' \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "username=jdoe" \
-d 'password=jdoe' \
-d 'grant_type=password' \
-d 'client_id=admin-cli' | jq -r '.access_token')
curl -H "Authorization: Bearer $token" localhost:8080/api/users/info
And you’ll see the response for the given token:
Access for subject jdoe is granted
Access UserResource with an invalid token
Run the following command:
token=$(curl https://raw.githubusercontent.com/redhat-developer-demos/quarkus-tutorial/master/jwt-token/quarkus.jwt.token -s)
curl -v -H "Authorization: Bearer $token" localhost:8080/api/users/info
And you’ll see the 401 Forbidden
response.
* Trying ::1...
* TCP_NODELAY set
* Connection failed
* connect to ::1 port 8080 failed: Connection refused
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /secure/claim HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.64.1
> Accept: */*
> Authorization: Bearer eyJraWQiOiJcL3ByaXZhdGVLZXkucGVtIiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiJqZG9lLXVzaW5nLWp3dC1yYmFjIiwiYXVkIjoidXNpbmctand0LXJiYWMiLCJ1cG4iOiJqZG9lQHF1YXJrdXMuaW8iLCJiaXJ0aGRhdGUiOiIyMDAxLTA3LTEzIiwiYXV0aF90aW1lIjoxNTcwMDk0MTcxLCJpc3MiOiJodHRwczpcL1wvcXVhcmt1cy5pb1wvdXNpbmctand0LXJiYWMiLCJyb2xlTWFwcGluZ3MiOnsiZ3JvdXAyIjoiR3JvdXAyTWFwcGVkUm9sZSIsImdyb3VwMSI6Ikdyb3VwMU1hcHBlZFJvbGUifSwiZ3JvdXBzIjpbIkVjaG9lciIsIlRlc3RlciIsIlN1YnNjcmliZXIiLCJncm91cDIiXSwicHJlZmVycmVkX3VzZXJuYW1lIjoiamRvZSIsImV4cCI6MjIwMDgxNDE3MSwiaWF0IjoxNTcwMDk0MTcxLCJqdGkiOiJhLTEyMyJ9.Hzr41h3_uewy-g2B-sonOiBObtcpkgzqmF4bT3cO58v45AIOiegl7HIx7QgEZHRO4PdUtR34x9W23VJY7NJ545ucpCuKnEV1uRlspJyQevfI-mSRg1bHlMmdDt661-V3KmQES8WX2B2uqirykO5fCeCp3womboilzCq4VtxbmM2qgf6ag8rUNnTCLuCgEoulGwTn0F5lCrom-7dJOTryW1KI0qUWHMMwl4TX5cLmqJLgBzJapzc5_yEfgQZ9qXzvsT8zeOWSKKPLm7LFVt2YihkXa80lWcjewwt61rfQkpmqSzAHL0QIs7CsM9GfnoYc0j9po83-P3GJiBMMFmn-vg
>
< HTTP/1.1 401 Unauthorized
< www-authenticate: Bearer
< content-length: 0
<
* Connection #0 to host localhost left intact
* Closing connection 0
Add incorrect RBAC to UserResource
package com.redhat.developers;
import javax.annotation.security.RolesAllowed;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import org.eclipse.microprofile.jwt.JsonWebToken;
@Path("/api/users")
public class UserResource {
@Inject
JsonWebToken jwt;
@GET
@Path("/info")
@RolesAllowed("Not-Subscriber")
public String getInfo() {
return "Access for subject " + jwt.getName() + " is granted";
}
}
Invoke the endpoint with incorrect RBAC
Run the following command:
token=$(curl https://raw.githubusercontent.com/redhat-developer-demos/quarkus-tutorial/master/jwt-token/quarkus.keycloak.jwt.token -s)
curl -v -H "Authorization: Bearer $token" localhost:8080/api/users/info
And you’ll see the 403 Forbidden
response.
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET /secure/claim HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.64.1
> Accept: */*
> Authorization: Bearer eyJraWQiOiJcL3ByaXZhdGVLZXkucGVtIiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiJqZG9lLXVzaW5nLWp3dC1yYmFjIiwiYXVkIjoidXNpbmctand0LXJiYWMiLCJ1cG4iOiJqZG9lQHF1YXJrdXMuaW8iLCJiaXJ0aGRhdGUiOiIyMDAxLTA3LTEzIiwiYXV0aF90aW1lIjoxNTcwMDk0MTcxLCJpc3MiOiJodHRwczpcL1wvcXVhcmt1cy5pb1wvdXNpbmctand0LXJiYWMiLCJyb2xlTWFwcGluZ3MiOnsiZ3JvdXAyIjoiR3JvdXAyTWFwcGVkUm9sZSIsImdyb3VwMSI6Ikdyb3VwMU1hcHBlZFJvbGUifSwiZ3JvdXBzIjpbIkVjaG9lciIsIlRlc3RlciIsIlN1YnNjcmliZXIiLCJncm91cDIiXSwicHJlZmVycmVkX3VzZXJuYW1lIjoiamRvZSIsImV4cCI6MjIwMDgxNDE3MSwiaWF0IjoxNTcwMDk0MTcxLCJqdGkiOiJhLTEyMyJ9.Hzr41h3_uewy-g2B-sonOiBObtcpkgzqmF4bT3cO58v45AIOiegl7HIx7QgEZHRO4PdUtR34x9W23VJY7NJ545ucpCuKnEV1uRlspJyQevfI-mSRg1bHlMmdDt661-V3KmQES8WX2B2uqirykO5fCeCp3womboilzCq4VtxbmM2qgf6ag8rUNnTCLuCgEoulGwTn0F5lCrom-7dJOTryW1KI0qUWHMMwl4TX5cLmqJLgBzJapzc5_yEfgQZ9qXzvsT8zeOWSKKPLm7LFVt2YihkXa80lWcjewwt61rfQkpmqSzAHL0QIs7CsM9GfnoYc0j9po83-P3GJiBMMFmn-vg
>
< HTTP/1.1 403 Forbidden
< Content-Length: 9
< Content-Type: application/octet-stream
<
* Connection #0 to host localhost left intact
Forbidden* Closing connection 0
Application Configuration
Although Dev Services are very useful when running Quarkus in Dev Mode, we need to think forward on how the application configuration will be available for production. This section explains how to persist the security configurations done earlier with Dev Services.
OpenID Connect extension allows you to define the adapter configuration using the application.properties
file which should be located at the src/main/resources
directory.
You can simply copy the configuration below to start working with the KeyCloak server:
# OIDC Configuration
quarkus.oidc.auth-server-url=http://localhost:8180/auth/realms/quarkus
quarkus.oidc.client-id=backend-service
quarkus.oidc.credentials.secret=secret
quarkus.oidc.tls.verification=none
quarkus.http.cors=true
# Enable Policy Enforcement
quarkus.keycloak.policy-enforcer.enable=true
Starting and Configuring the Keycloak Server
You can start a Keycloak Server with Docker by running the following command:
docker run --name keycloak -e DB_VENDOR=H2 -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin -p 8180:8080 quay.io/keycloak/keycloak:15.0.2
Go to http://localhost:8180/auth and use admin
for user and password.
We will add our own user, role and group by following these steps:
-
Click on Import.
-
Import the realm from https://raw.githubusercontent.com/redhat-developer-demos/quarkus-tutorial/master/jwt-token/quarkus-realm.json. After this step you should have
Subscriber
Role and Group, together withjdoe
user correctly configured.