ifndef::sourcedir-code[:sourcedir-code: ../labs/openid-connect-policies/src/main/java/at/htl] ifndef::sourcedir-test[:sourcedir-test: ../labs/openid-connect-policies/src/test/java/at/htl] icon:file-text-o[link=https://raw.githubusercontent.com/htl-leonding-college/quarkus-security-lecture-notes/master/asciidocs/{docname}.adoc] ‏ ‏ ‎ icon:github-square[link=https://github.com/htl-leonding-college/quarkus-security-lecture-notes] ‏ ‏ ‎ icon:home[link=https://htl-leonding.github.io/] == Authorization with Policies see also https://quarkus.io/guides/security-keycloak-authorization[Quarkus - Using OpenID Connect and Keycloak to Centralize Authorizations] == 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" ---- == Add the Endpoint (Resource) .UserResource.java [source,java] ---- include::{sourcedir-code}/UserResource.java[] ---- == Application configuration .application.properties [source,properties,options="nowrap"] ---- include::{sourcedir-code}/../../../resources/application.properties[] ---- === 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. [source,properties] ---- # 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 ---- == Make the Initial Commit [source,shell] ---- cd openid-connect-policies git init git add . git commit -m "inital commit" git remote add origin https://github.com//openid-connect-policies.git git push -u origin master idea . ---- == Add a extension [source,shell] ---- ./mvnw quarkus:add-extension -Dextensions="keycloak-authorization" ---- == Start Keycloak [source,shell] ---- 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 == Configure Keycloak -> Administration Console * Add realm ** Name: quarkus ** kbd:[Create] * Clients ** kbd:[Create] *** ClientID: my-backend-service *** Client Protocol: openid-connect *** Click kbd:[Save] * Users ** kbd:[Add user] *** Details **** Username: admin **** Email Verified: ON **** kbd:[Save] *** Credentials **** Password: passme **** Password Confirmation: passme **** Temporary: OFF **** kbd:[Set Password] **** Are you sure you want to set a password for the user?: kbd:[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/ *** kbd:[Save] ** Credentials *** Copy Secret into clipboard *** Paste the secret in application.properties in the line "quarkus.oidc.credentials.secret" == 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 on `Add Environmental File` -> Option `Regular` Now a file called `http-client.env.json` was created: We define some variables: .http-client.env.json ---- { "dev": { "keycloak-host": "http://localhost:8180", "quarkus-host": "http://localhost:8080", "username": "my-backend-service", "password": "97fdb5b3-6fff-4090-966a-0f1c7355d0ba" // <.> } } ---- <.> use your secret .requests.http ---- 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. == Configure Resources * Clients -> my-backend-service ** Authorization *** Resources **** Actions - Delete the Default Resource -> Confirm the deletion **** kbd:[Create] **** Add Resource ***** Name: Users resource ***** Display name: Users resource ***** URI: /api/users/* ***** kbd:[Save] **** kbd:[Create] **** Add Resource ***** Name: Admin resource ***** Display name: Admin resource ***** URI: /api/admin/* ***** kbd:[Save] image:clients-authorization-resources.png[] * Roles ** kbd:[Add Role] *** Role Name: user *** kbd:[Save] ** kbd:[Add Role] *** Role Name: admin *** kbd:[Save] * Users ** kbd:[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 ***** kbd:[Save] **** Create Policy ... -> Role ***** Name: Admin policy ***** Description: Ability to use admins resources ***** Realm Roles: admin ***** kbd:[Save] *** Permissions **** Default Permission -> kbd:[Delete] -> Confirm Deletion **** Create Permission... -> Resource-Based ***** Name: Users permission ***** Resources: Users resource ***** Apply Policy: Users policy ***** kbd:[Save] **** Create Permission... -> Resource-Based ***** Name: Admins permission ***** Resources: Admins resource ***** Apply Policy: Admins policy ***** kbd:[Save] image:clients-authorization-permissions.png[] == Test the Access to the Resources === Resource admin is unauthorized (w/o any authorization) .Request with variable ---- GET {{quarkus-host}}/api/admin ---- .Request + Response ---- GET http://localhost:8080/api/admin HTTP/1.1 401 Unauthorized content-length: 0 Response code: 401 (Unauthorized); Time: 644ms; Content length: 0 bytes ---- === Resource users is unauthorized (w/o any authorization) .Request with variable ---- GET {{quarkus-host}}/api/users ---- .Request + Response ---- GET http://localhost:8080/api/users HTTP/1.1 401 Unauthorized content-length: 0 Response code: 401 (Unauthorized); Time: 54ms; Content length: 0 bytes ---- === User 'user' access Resource 'users' ==== Retrieves access token ---- 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 > {% client.global.set("auth_token", response.body.access_token); %} # <.> ---- <.> you have to provide: `Authorization: Basic {client-id} {secret}` <.> the access-token is saved in a variable `auth_token`, so the next request can use it ==== Accesses the users-Resource - 200 ---- GET {{quarkus-host}}/api/users Authorization: Bearer {{auth_token}} ---- ==== Accesses the admin-Resource - 403 .Request with variable ---- GET {{quarkus-host}}/api/admin Authorization: Bearer {{auth_token}} ---- .Request + Response ---- GET http://localhost:8080/api/admin HTTP/1.1 403 Forbidden content-length: 0 ---- === User 'admin' access Resource 'users' ==== Retrieves access token ---- POST {{keycloak-host}}/auth/realms/quarkus/protocol/openid-connect/token Authorization: Basic {{username}} {{password}} # <.> Content-Type: application/x-www-form-urlencoded username=admin&password=passme&grant_type=password # <.> > {% client.global.set("auth_token", response.body.access_token); %} ---- <.> you have to provide: `Authorization: Basic {client-id} {secret}` <.> the access-token is saved in a variable `auth_token`, so the next request can use it ==== Accesses the users-Resource - 200 ---- GET {{quarkus-host}}/api/users Authorization: Bearer {{auth_token}} ---- ==== Accesses the admin-Resource - 200 .Request with variable ---- GET {{quarkus-host}}/api/admin Authorization: Bearer {{auth_token}} ---- .Request + Response ---- GET http://localhost:8080/api/admin HTTP/1.1 200 OK Cache-Control: no-cache Content-Length: 10 Content-Type: application/json I am admin ----