1. Keycloak Role-Based Access Control (RBAC)
1.1. Create your Quarkus Project
mvn io.quarkus:quarkus-maven-plugin:2.4.2.Final:create \ -DprojectGroupId=at.htl \ -DprojectArtifactId=quarkus-openid-connect \ -Dextensions="oidc, resteasy-jsonb"
1.2. Add the Endpoint (Resource)
Unresolved directive in keycloak-rbac.adoc - include::../labs/openid-connect-policies/src/main/java/at/htl/UserResource.java[]
1.3. Application configuration
Unresolved directive in keycloak-rbac.adoc - include::../labs/openid-connect-policies/src/main/java/at/htl/../../../resources/application.properties[]
1.3.1. Problems, when using Jackson
-
In this example, we serialize not a self-built entity class. We serialize a given interface
SecurityIdentity
.-
An error occurs because of lacking getter and setter.
-
So we have to loosen the policy for serializing this interface.
-
# these properties are necessary because jackson throws an 'InvalidDefinitionException: No serializer found for class'
# https://quarkus.io/guides/rest-json#json
# würde man json-b verwenden, wäre das nicht notwendig
quarkus.jackson.fail-on-unknown-properties=false
quarkus.jackson.fail-on-empty-beans=false
1.4. Make the Initial Commit
cd openid-connect-policies
git init
git add .
git commit -m "inital commit"
git remote add origin https://github.com/<your github-account>/openid-connect-policies.git
git push -u origin master
idea .
1.6. Start Keycloak
docker run --name keycloak -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin -e DB_VENDOR=h2 -p 8180:8080 jboss/keycloak:15.0.2
Open in Browser: http://localhost:8180/auth
1.7. Configure Keycloak
→ Administration Console
-
Add realm
-
Name: quarkus
-
Create
-
-
Clients
-
Create
-
ClientID: my-backend-service
-
Client Protocol: openid-connect
-
Click Save
-
-
-
Users
-
Add user
-
Details
-
Username: admin
-
Email Verified: ON
-
Save
-
-
Credentials
-
Password: passme
-
Password Confirmation: passme
-
Temporary: OFF
-
Set Password
-
Are you sure you want to set a password for the user?: Set password
-
-
-
Now make a User "user" like "admin".
-
Clients → my-backend-service
-
Settings
-
Access Type: confidential
-
Service Accounts Enabled: ON
-
Authorization Enabled: OFF
-
Valid Redirect URIs: http://localhost:8080/
-
Save
-
-
Credentials
-
Copy Secret into clipboard
-
Paste the secret in application.properties in the line "quarkus.oidc.credentials.secret"
-
-
1.8. Access Keycloak
Now we will access Keycloak for the first time and retrieve the access token.
-
Create a folder called
http-request
in the project root. -
Create a file called
requests.http
(the http-ending is important) -
Open the file
requests.http
and click onAdd Environmental File
→ OptionRegular
Now a file called http-client.env.json
was created:
We define some variables:
{ "dev": { "keycloak-host": "http://localhost:8180", "quarkus-host": "http://localhost:8080", "username": "my-backend-service", "password": "97fdb5b3-6fff-4090-966a-0f1c7355d0ba" (1) } }
1 | use your secret |
POST {{keycloak-host}}/auth/realms/quarkus/protocol/openid-connect/token Authorization: Basic {{username}} {{password}} Content-Type: application/x-www-form-urlencoded username=user&password=passme&grant_type=password
The output shows status code 201 and the access token.
1.9. Configure Resources
-
Clients → my-backend-service
-
Authorization
-
Resources
-
Actions - Delete the Default Resource → Confirm the deletion
-
Create
-
Add Resource
-
Name: Users resource
-
Display name: Users resource
-
URI: /api/users/*
-
Save
-
-
Create
-
Add Resource
-
Name: Admin resource
-
Display name: Admin resource
-
URI: /api/admin/*
-
Save
-
-
-
-
-
Roles
-
Add Role
-
Role Name: user
-
Save
-
-
Add Role
-
Role Name: admin
-
Save
-
-
-
Users
-
View all users
-
Click on ID of user 'user'
-
Role Mappings
-
add Role 'user' to Assigned Roles
-
-
-
Click on ID of user 'admin'
-
Role Mappings
-
add Roles 'user' and
admin
to Assigned Roles
-
-
-
-
Clients → my-backend-service
-
Authorization
-
Policies
-
Create Policy … → Role
-
Name: Users policy
-
Description: Ability to use users resources
-
Realm Roles: user
-
Save
-
-
Create Policy … → Role
-
Name: Admin policy
-
Description: Ability to use admins resources
-
Realm Roles: admin
-
Save
-
-
-
Permissions
-
Default Permission → Delete → Confirm Deletion
-
Create Permission… → Resource-Based
-
Name: Users permission
-
Resources: Users resource
-
Apply Policy: Users policy
-
Save
-
-
Create Permission… → Resource-Based
-
Name: Admins permission
-
Resources: Admins resource
-
Apply Policy: Admins policy
-
Save
-
-
-
-
1.10. Test the Access to the Resources
1.10.1. Resource admin is unauthorized (w/o any authorization)
GET {{quarkus-host}}/api/admin
GET http://localhost:8080/api/admin HTTP/1.1 401 Unauthorized content-length: 0 <Response body is empty> Response code: 401 (Unauthorized); Time: 644ms; Content length: 0 bytes
1.10.2. Resource users is unauthorized (w/o any authorization)
GET {{quarkus-host}}/api/users
GET http://localhost:8080/api/users HTTP/1.1 401 Unauthorized content-length: 0 <Response body is empty> Response code: 401 (Unauthorized); Time: 54ms; Content length: 0 bytes
1.11. User 'user' access Resource 'users'
1.11.1. Retrieves access token
POST {{keycloak-host}}/auth/realms/quarkus/protocol/openid-connect/token Authorization: Basic {{username}} {{password}} (1) Content-Type: application/x-www-form-urlencoded username=user&password=passme&grant_type=password > {% client.global.set("auth_token", response.body.access_token); %} (2)
1 | you have to provide: Authorization: Basic {client-id} {secret} |
2 | the access-token is saved in a variable auth_token , so the next request can use it |
1.12. User 'admin' access Resource 'users'
1.12.1. Retrieves access token
POST {{keycloak-host}}/auth/realms/quarkus/protocol/openid-connect/token Authorization: Basic {{username}} {{password}} (1) Content-Type: application/x-www-form-urlencoded username=admin&password=passme&grant_type=password (2) > {% client.global.set("auth_token", response.body.access_token); %}
1 | you have to provide: Authorization: Basic {client-id} {secret} |
2 | the access-token is saved in a variable auth_token , so the next request can use it |