Quarkus + KeyCloakの SSO 連携が簡単らしい。
Quarkus は JBoss 御謹製のK8sネイティブなマイクロサービスが素早く作れるフレームワークでございまして GraalVM でのビルドが簡単にできるらしいのでちょっと注目をしておりましたところ、以下のような記事を見つけました。
Quarkus は RedHat 社が開発しており、同社製または同社がスポンサーの OSS 製品(KeyCloak、Hibernate)との連携を前提としている。
あらそうなの?KeyCloakの代替になる SSO の OSS はほぼないのでKeyCloakと接続前提なのは全然、問題なしでございますよ~、ということで早速ググってみたところ、あっさりドンピシャなサンプルを発見いたしました。
ただしこちらの手順では JDK
やら maven
などの物騒なブツが必要なので、ちょっと Docker さんところでお願いしたいなぁ・・・というわけで、いつもの docker-compose を使ってさくっと起動するように調整してみました。
0. 準備
docker、docker-compose を使用します。こちらのご用意をお願いいたします。
続いてソースコードを git clone
してきます。
$ git clone git@github.com:yuhaibohotmail/quarkus-keycloak-demo.git
...
$ cd quarkus-keycloak-demo
クローンが終わったら中に入ります。
以降の手順では quarkus-keycloak-demo
内での作業となります。
1. Quarkus サンプルのビルド用イメージを準備
readme.md の "Build" および "Run with Remote Debug" の記述が Quarkus 側のサンプルプログラムのビルド & 起動コマンドとなっております。
まず、ビルドするための環境を Dockerイメージで用意します。
FROM maven
WORKDIR /tmp/build
ADD . /tmp/build
RUN mvn clean package -DskipTests
これでこのイメージの中にはビルドした jar が入っている、ということになります。
続いてこちらを以下のように docker-compose.yml から起動するようにします。
version : "3"
services:
quarkus:
build:
context: .
ports:
- 8787:8787
- 8082:8082
entrypoint: >
java
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=0.0.0.0:8787
-jar target/quarkus-keycloak-demo-1.0.0-SNAPSHOT-runner.jar
./target/classes
./target/wiring-devmode
./target/transformer-cache
tty: true
この Quarkus のサーバーは 8082 で動くように設定されているので、リモートデバッグ用の 8787 と 8082 は外向けにポートを開けておきます。(今回のデモでは必要ないですが。。。)
まだ、ビルドしてはいけません! clone してきたこのプロジェクトには ".dockerignore" ファイルがあります!これをチェックしてからでないと危険です!(・・・はい、数時間ハマりました。)
* # <- これを削除するべし!!
!target/*-runner
!target/*-runner.jar
!target/lib/*
というわけで、".dockerignore" の *
は削除しておいてください。
そしてビルドはまだ!です。
2. KeyCloak サービスの追加
続いて、上記の docker-compose.yml
に KeyCloak 用のサービスを追加します。
readme.md の Run Keycloak
の箇所も Docker コマンドで KeyCloak を起動する手順ですのでこちらをそのまま docker-compose 用に書き換えます。
...(上記の続き)
keycloak:
image: jboss/keycloak:5.0.0
container_name: keycloak
restart: always
ports:
- 8180:8180
environment:
- KEYCLOAK_USER=admin
- KEYCLOAK_PASSWORD=admin
volumes:
- ./quarkus-quickstart-realm.json:/config/quarkus-quickstart-realm.json
command: >
-b 0.0.0.0
-Djboss.http.port=8180
-Dkeycloak.migration.action=import
-Dkeycloak.migration.provider=singleFile
-Dkeycloak.migration.file=/config/quarkus-quickstart-realm.json
-Dkeycloak.migration.strategy=OVERWRITE_EXISTING
tty: true
はい、こちらは簡単ですね~。
3. Quarkus、KeyCloak の連携設定を修正
さて、双方の連携する設定を見直す必要があります。元の手順では Quarkus を実機で動かしているため localhost
で問題なく動いておりますが、Quarkus をコンテナ化してしまったので、Quarkus -> KeyCloak のリクエストが localhost
じゃ名前解決できなくなっております。
また、docker-compose.yml で設定したサービス名で名前解決したいので、docker-compose のネットワークに参加したコンテナ内でデモを動かしてみたいと思います。
というわけで、すべて localhost
で指定していた箇所をそれぞれのサービス名に変更する必要があります。
3-1. Quarkus の設定
まずは、Quarkus から Keycloak を参照する設定は以下です。
# Configuration file
quarkus.http.port=8082
# MP-JWT Config
mp.jwt.verify.publickey.location=http://keycloak:8180/auth/realms/quarkus-quickstart/protocol/openid-connect/certs
mp.jwt.verify.issuer=http://keycloak:8180/auth/realms/quarkus-quickstart
quarkus.smallrye-jwt.auth-mechanism=MP-JWT
quarkus.smallrye-jwt.realmName=quarkus-keycloak-demo
quarkus.smallrye-jwt.enabled=true
上記ファイルの localhost:8180
を keycloak:8180
に修正いたしました。
3-2. KeyCloak の設定
KeyCloak の設定を quarkus-quickstart-realm.json
で投入していますが、ここでは Quarkus 側のアドレスが、localhost:8080
となっています。(ポートも違うし。。。)
これを修正していきます。
...
"clientId": "quarkus-front",
"rootUrl": "http://quarkus:8082",
"adminUrl": "http://quarkus:8082",
"surrogateAuthRequired": false,
"enabled": true,
"clientAuthenticatorType": "client-secret",
"secret": "**********",
"redirectUris": [
"http://quarkus:8082/*"
],
"webOrigins": [
"http://quarkus:8082"
],
"notBefore": 0,
...
localhost:8080
を quarkus:8082
に修正いたしました。
3. デモ実行用コンテナの追加
最後にデモを実行するための空っぽのコンテナを追加いたします。
... (上記の続き)
demo:
image: ubuntu
volumes:
- ./retrieve.sh:/retrieve.sh
で、"retrieve.sh" には readme.md の "Retrieve Tokens" の手順をそのままコピペしておきます。が、ここも localhost
はそれぞれのサービス名に修正します。
#!/bin/bash
KC_CLIENT_ID=quarkus-front
KC_ISSUER=http://keycloak:8180/auth/realms/quarkus-quickstart
# Simple test user
KC_USERNAME=test
KC_PASSWORD=test
KC_RESPONSE=$( \
curl \
-d "client_id=$KC_CLIENT_ID" \
-d "username=$KC_USERNAME" \
-d "password=$KC_PASSWORD" \
-d "grant_type=password" \
"$KC_ISSUER/protocol/openid-connect/token" \
)
echo $KC_RESPONSE | jq -C .
KC_ACCESS_TOKEN=$(echo $KC_RESPONSE | jq -r .access_token)
# Try to call endpoints - /data/user should work, /data/admin should fail
curl -v -H "Authorization: Bearer $KC_ACCESS_TOKEN" http://quarkus:8082/data/user
curl -v -H "Authorization: Bearer $KC_ACCESS_TOKEN" http://quarkus:8082/data/admin
# Simple admin user
KC_USERNAME=admin
KC_PASSWORD=test
KC_RESPONSE=$( \
curl \
-d "client_id=$KC_CLIENT_ID" \
-d "username=$KC_USERNAME" \
-d "password=$KC_PASSWORD" \
-d "grant_type=password" \
"$KC_ISSUER/protocol/openid-connect/token" \
)
echo $KC_RESPONSE | jq -C .
KC_ACCESS_TOKEN=$(echo $KC_RESPONSE | jq -r .access_token)
# Try to call endpoints - /data/user and /data/admin should work both
curl -v -H "Authorization: Bearer $KC_ACCESS_TOKEN" http://quarkus:8082/data/admin
curl -v -H "Authorization: Bearer $KC_ACCESS_TOKEN" http://quarkus:8082/data/user
4. ビルド&実行
さて、準備が整ったところで docker-compose を起動します。初回は自動的にビルドも実行されるので up
だけでOKです。
$ docker-compose up -d
...
ビルドを再度、実行したい場合は build
します。
$ docker-compose build
...
5. デモ用コンテナに入って準備・・・
デモ実行用の demo
コンテナはエントリーポイントを置いてないのでさっさと終わってしまいますので、exec
コマンドではなく、直接 run
で中に入ります。
$ docker-compose run demo
root@xxxxxx:/#
で、今回は Ubuntu のすっぴんイメージを使ってしまったので、curl
も jq
もこの中には入っていません。(ちょっと手抜きすぎた。。。)
皆様におかれましては是非とも jq
導入済みのイメージをご利用ください。今回は apt
しちゃいます。
/# apt update && apt upgrade -y
...
/# apt install curl jq -y
...
curl
と jq
コマンドが入ったら retrieve.sh
を叩いてみましょう。
6. デモプログラムの実行
retrieve.sh に記載した手順はちょっとストレートすぎるので、readme.md の Demo
セクションに目を通してみましょう。
Make a request as user
test
and call thehttp://localhost:8082/data/user
endpoint, (see Retrieve Tokens)
This will output
data for user "test"
Calling the
http://localhost:8082/data/admin
endpoint correctly fails with:
Access forbidden: role not allowed%
Make request as user
admin
and call thehttp://localhost:8082/data/user
endpoint again
This will output:
data for user "admin"
calling
http://localhost:8082/data/admin
endpoint succeeds now
data for admin "admin"
要は・・・
- "test" ユーザーでアクセストークン取得して"/user"にアクセスすると
data for user "test"
と返ってくるが、"/admin" にアクセスすると "Access forbidden" で怒られる。 - "admin" ユーザーでアクセストークン取得して"/user"にアクセスすると
data for user "admin"
と返ってくるし、"/admin" にアクセスしてもちゃんとdata for admin "admin"
と返ってくる。
・・・ということですね!(そのまんま)
ん~?2.の手順の説明と curl でアクセスするパスが逆ですね・・・ここはちょっと気を付けてみましょう。
では、さっそくデモバッチを流してみます。ログのコメントは分かりやすいように追記しております。
$ ./retrieve.sh
# ↓ まずは "test" ユーザーでKeyCloakにログインし、アクセストークンを取得する
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 2621 100 2550 100 71 5730 159 --:--:-- --:--:-- --:--:-- 5889
# ↓ は取得したアクセストークンの表示
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJ1aW81bFppVU9tN19RZ1JISTRYOWpwRXFlVkN3THBfekZMOWxUMWJ5TkR3In0.eyJqdGkiOiJiZGVjNTIyZC1lNjQ1LTRmY2YtYTY4Zi1iMTEzODJhYTJjOWQiLCJleHAiOjE1NzIzMzQ5MDMsIm5iZiI6MCwiaWF0IjoxNTcyMzM0NjAzLCJpc3MiOiJodHRwOi8va2V5Y2xvYWs6ODE4MC9hdXRoL3JlYWxtcy9xdWFya3VzLXF1aWNrc3RhcnQiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiYTE5YjJhZmMtZTk2ZS00OTM5LTgyYmYtYWE0YjU4OWRlMTM2IiwidHlwIjoiQmVhcmVyIiwiYXpwIjoicXVhcmt1cy1mcm9udCIsImF1dGhfdGltZSI6MCwic2Vzc2lvbl9zdGF0ZSI6ImJkYTQwNjg3LWM2NjMtNGEzZS04MjA0LWVhYTM2ZWM0NDUyZiIsImFjciI6IjEiLCJhbGxvd2VkLW9yaWdpbnMiOlsiaHR0cDovL3F1YXJrdXM6ODA4MiJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiIsInVzZXIiXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6ImVtYWlsIHByb2ZpbGUiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsIm5hbWUiOiJUaGVvIFRlc3RlciIsImdyb3VwcyI6WyJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIiwidXNlciJdLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJ0ZXN0IiwiZ2l2ZW5fbmFtZSI6IlRoZW8iLCJmYW1pbHlfbmFtZSI6IlRlc3RlciIsImVtYWlsIjoidGVzdGVyQGxvY2FsaG9zdCJ9.XHPcdZ9Zd-lBRiXWU4KHmzhe6d_EtNsckjYh65tq0tDU1C5w5dX-K5PpR-zQveBHmaFIjKMtpeunQtR4iIil5oRskUpok7kCHbBRlbpOt_padLIsgNAy2TddFRHS0jlxsVMnigqGKmD6TSAA86e1MM3xrP2VxQKyVRiRQA9_m67mP99fUBsO13j5oZ2VcPz_WAvfFmhgIeTAi-OV0LCUUIpnudB3IHHUQXRazZUbXVr00c_TryWSf8-__TProbGZ-B8pmPh8jfOfTyZQ1DKy68rBNy2AcEYe1pfnVvK6cMMtqFvabdNqgvh83NZx20XR4LHAPauVbG4UZlnNILk5Dg",
"expires_in": 300,
"refresh_expires_in": 1800,
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI1ZjA0YmYwOC0yODMzLTRlN2EtOTg1MC0yZmVmNWJjYmY3YzYifQ.eyJqdGkiOiIyNGNiNDdmNS00ZWU5LTQ4YWMtODVlMS0xYzJjNjc4ZjQ1NGEiLCJleHAiOjE1NzIzMzY0MDMsIm5iZiI6MCwiaWF0IjoxNTcyMzM0NjAzLCJpc3MiOiJodHRwOi8va2V5Y2xvYWs6ODE4MC9hdXRoL3JlYWxtcy9xdWFya3VzLXF1aWNrc3RhcnQiLCJhdWQiOiJodHRwOi8va2V5Y2xvYWs6ODE4MC9hdXRoL3JlYWxtcy9xdWFya3VzLXF1aWNrc3RhcnQiLCJzdWIiOiJhMTliMmFmYy1lOTZlLTQ5MzktODJiZi1hYTRiNTg5ZGUxMzYiLCJ0eXAiOiJSZWZyZXNoIiwiYXpwIjoicXVhcmt1cy1mcm9udCIsImF1dGhfdGltZSI6MCwic2Vzc2lvbl9zdGF0ZSI6ImJkYTQwNjg3LWM2NjMtNGEzZS04MjA0LWVhYTM2ZWM0NDUyZiIsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIiwidXNlciJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoiZW1haWwgcHJvZmlsZSJ9._jkMbiSCk7j3qogzWhUzY8G5piVCxE05QLjXuaCHouA",
"token_type": "bearer",
"not-before-policy": 0,
"session_state": "bda40687-c663-4a3e-8204-eaa36ec4452f",
"scope": "email profile"
}
# ↓ 上記のアクセストークンで /data/user にアクセス
* Trying 172.28.0.3...
* TCP_NODELAY set
* Connected to quarkus (172.28.0.3) port 8082 (#0)
> GET /data/user HTTP/1.1
> Host: quarkus:8082
> User-Agent: curl/7.58.0
> Accept: */*
> Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJ1aW81bFppVU9tN19RZ1JISTRYOWpwRXFlVkN3THBfekZMOWxUMWJ5TkR3In0.eyJqdGkiOiJiZGVjNTIyZC1lNjQ1LTRmY2YtYTY4Zi1iMTEzODJhYTJjOWQiLCJleHAiOjE1NzIzMzQ5MDMsIm5iZiI6MCwiaWF0IjoxNTcyMzM0NjAzLCJpc3MiOiJodHRwOi8va2V5Y2xvYWs6ODE4MC9hdXRoL3JlYWxtcy9xdWFya3VzLXF1aWNrc3RhcnQiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiYTE5YjJhZmMtZTk2ZS00OTM5LTgyYmYtYWE0YjU4OWRlMTM2IiwidHlwIjoiQmVhcmVyIiwiYXpwIjoicXVhcmt1cy1mcm9udCIsImF1dGhfdGltZSI6MCwic2Vzc2lvbl9zdGF0ZSI6ImJkYTQwNjg3LWM2NjMtNGEzZS04MjA0LWVhYTM2ZWM0NDUyZiIsImFjciI6IjEiLCJhbGxvd2VkLW9yaWdpbnMiOlsiaHR0cDovL3F1YXJrdXM6ODA4MiJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiIsInVzZXIiXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6ImVtYWlsIHByb2ZpbGUiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsIm5hbWUiOiJUaGVvIFRlc3RlciIsImdyb3VwcyI6WyJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIiwidXNlciJdLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJ0ZXN0IiwiZ2l2ZW5fbmFtZSI6IlRoZW8iLCJmYW1pbHlfbmFtZSI6IlRlc3RlciIsImVtYWlsIjoidGVzdGVyQGxvY2FsaG9zdCJ9.XHPcdZ9Zd-lBRiXWU4KHmzhe6d_EtNsckjYh65tq0tDU1C5w5dX-K5PpR-zQveBHmaFIjKMtpeunQtR4iIil5oRskUpok7kCHbBRlbpOt_padLIsgNAy2TddFRHS0jlxsVMnigqGKmD6TSAA86e1MM3xrP2VxQKyVRiRQA9_m67mP99fUBsO13j5oZ2VcPz_WAvfFmhgIeTAi-OV0LCUUIpnudB3IHHUQXRazZUbXVr00c_TryWSf8-__TProbGZ-B8pmPh8jfOfTyZQ1DKy68rBNy2AcEYe1pfnVvK6cMMtqFvabdNqgvh83NZx20XR4LHAPauVbG4UZlnNILk5Dg
>
< HTTP/1.1 200 OK
< Connection: keep-alive
< Content-Type: text/plain;charset=UTF-8
< Content-Length: 20
< Date: Tue, 29 Oct 2019 07:36:43 GMT
<
* Connection #0 to host quarkus left intact
data for user "test"
# ↑ ログイン成功!ちゃんと "test" ユーザーが認識されました。
# ↓ /data/admin にアクセスしてみると・・・
* Trying 172.28.0.3...
* TCP_NODELAY set
* Connected to quarkus (172.28.0.3) port 8082 (#0)
> GET /data/admin HTTP/1.1
> Host: quarkus:8082
> User-Agent: curl/7.58.0
> Accept: */*
> Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJ1aW81bFppVU9tN19RZ1JISTRYOWpwRXFlVkN3THBfekZMOWxUMWJ5TkR3In0.eyJqdGkiOiJiZGVjNTIyZC1lNjQ1LTRmY2YtYTY4Zi1iMTEzODJhYTJjOWQiLCJleHAiOjE1NzIzMzQ5MDMsIm5iZiI6MCwiaWF0IjoxNTcyMzM0NjAzLCJpc3MiOiJodHRwOi8va2V5Y2xvYWs6ODE4MC9hdXRoL3JlYWxtcy9xdWFya3VzLXF1aWNrc3RhcnQiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiYTE5YjJhZmMtZTk2ZS00OTM5LTgyYmYtYWE0YjU4OWRlMTM2IiwidHlwIjoiQmVhcmVyIiwiYXpwIjoicXVhcmt1cy1mcm9udCIsImF1dGhfdGltZSI6MCwic2Vzc2lvbl9zdGF0ZSI6ImJkYTQwNjg3LWM2NjMtNGEzZS04MjA0LWVhYTM2ZWM0NDUyZiIsImFjciI6IjEiLCJhbGxvd2VkLW9yaWdpbnMiOlsiaHR0cDovL3F1YXJrdXM6ODA4MiJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiIsInVzZXIiXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6ImVtYWlsIHByb2ZpbGUiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsIm5hbWUiOiJUaGVvIFRlc3RlciIsImdyb3VwcyI6WyJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIiwidXNlciJdLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJ0ZXN0IiwiZ2l2ZW5fbmFtZSI6IlRoZW8iLCJmYW1pbHlfbmFtZSI6IlRlc3RlciIsImVtYWlsIjoidGVzdGVyQGxvY2FsaG9zdCJ9.XHPcdZ9Zd-lBRiXWU4KHmzhe6d_EtNsckjYh65tq0tDU1C5w5dX-K5PpR-zQveBHmaFIjKMtpeunQtR4iIil5oRskUpok7kCHbBRlbpOt_padLIsgNAy2TddFRHS0jlxsVMnigqGKmD6TSAA86e1MM3xrP2VxQKyVRiRQA9_m67mP99fUBsO13j5oZ2VcPz_WAvfFmhgIeTAi-OV0LCUUIpnudB3IHHUQXRazZUbXVr00c_TryWSf8-__TProbGZ-B8pmPh8jfOfTyZQ1DKy68rBNy2AcEYe1pfnVvK6cMMtqFvabdNqgvh83NZx20XR4LHAPauVbG4UZlnNILk5Dg
>
< HTTP/1.1 403 Forbidden
< Connection: keep-alive
< Content-Type: text/html;charset=UTF-8
< Content-Length: 34
< Date: Tue, 29 Oct 2019 07:36:43 GMT
<
* Connection #0 to host quarkus left intact
Access forbidden: role not allowed
# ↑ アクセスが拒否されました。
# ↓ 続いて admin ユーザーのトークンを取得
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 2652 100 2580 100 72 20640 576 --:--:-- --:--:-- --:--:-- 21216
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJ1aW81bFppVU9tN19RZ1JISTRYOWpwRXFlVkN3THBfekZMOWxUMWJ5TkR3In0.eyJqdGkiOiIyNWRjOTI1Zi03YWNjLTRmMmMtOWI4ZS0zNjBkOWU0YmFiMzIiLCJleHAiOjE1NzIzMzQ5MDMsIm5iZiI6MCwiaWF0IjoxNTcyMzM0NjAzLCJpc3MiOiJodHRwOi8va2V5Y2xvYWs6ODE4MC9hdXRoL3JlYWxtcy9xdWFya3VzLXF1aWNrc3RhcnQiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiMDc3M2IxZTYtYWMwMy00Y2VjLTllNjctNTYwNzFhNzJlOTlkIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoicXVhcmt1cy1mcm9udCIsImF1dGhfdGltZSI6MCwic2Vzc2lvbl9zdGF0ZSI6IjQ5ZDNkNTU0LTg3ZjgtNGI0Zi05ZTkzLWQwNDM2MTFiNzBiMiIsImFjciI6IjEiLCJhbGxvd2VkLW9yaWdpbnMiOlsiaHR0cDovL3F1YXJrdXM6ODA4MiJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsib2ZmbGluZV9hY2Nlc3MiLCJhZG1pbiIsInVtYV9hdXRob3JpemF0aW9uIiwidXNlciJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoiZW1haWwgcHJvZmlsZSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwibmFtZSI6IkFybm8gQWRtaW4iLCJncm91cHMiOlsib2ZmbGluZV9hY2Nlc3MiLCJhZG1pbiIsInVtYV9hdXRob3JpemF0aW9uIiwidXNlciJdLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJhZG1pbiIsImdpdmVuX25hbWUiOiJBcm5vIiwiZmFtaWx5X25hbWUiOiJBZG1pbiIsImVtYWlsIjoiYWRtaW5AbG9jYWxob3N0In0.nUT8IU7jLNuN_00-yN8BccXAdYedJIyW-_dNY5ICPJR8bgWoie10TJpiwGdij6cOc5TsecSjuT6BvivqXgwbbOyTnh3Fs2ATXtbVFJI2WKfP4f2bbie_fBvRCD2wRfkpR5wGt3q9eceXwMOp0fh2ExvjJ3HgY_zhmerQpyZljeU6-YiF8JptiKRCrmD-USwmc0onQ50KKjWAFrhUYKcOAx7XTT2rHFspJrVPmbOhaSV6LCan4cqalbnfIgwoU8hVssvvW6DLdIEDjwEgrksmmKpT6nkl4sTMqFXVcbUA_UBhEopAEzts9DKEJZAGWXLqz-prt3wzFJm-Jws__2njog",
"expires_in": 300,
"refresh_expires_in": 1800,
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI1ZjA0YmYwOC0yODMzLTRlN2EtOTg1MC0yZmVmNWJjYmY3YzYifQ.eyJqdGkiOiI5MGEzMDVkMi1kMzcyLTRjOTItOWJjMy0wNDA5OWIyYjg4YzIiLCJleHAiOjE1NzIzMzY0MDMsIm5iZiI6MCwiaWF0IjoxNTcyMzM0NjAzLCJpc3MiOiJodHRwOi8va2V5Y2xvYWs6ODE4MC9hdXRoL3JlYWxtcy9xdWFya3VzLXF1aWNrc3RhcnQiLCJhdWQiOiJodHRwOi8va2V5Y2xvYWs6ODE4MC9hdXRoL3JlYWxtcy9xdWFya3VzLXF1aWNrc3RhcnQiLCJzdWIiOiIwNzczYjFlNi1hYzAzLTRjZWMtOWU2Ny01NjA3MWE3MmU5OWQiLCJ0eXAiOiJSZWZyZXNoIiwiYXpwIjoicXVhcmt1cy1mcm9udCIsImF1dGhfdGltZSI6MCwic2Vzc2lvbl9zdGF0ZSI6IjQ5ZDNkNTU0LTg3ZjgtNGI0Zi05ZTkzLWQwNDM2MTFiNzBiMiIsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJvZmZsaW5lX2FjY2VzcyIsImFkbWluIiwidW1hX2F1dGhvcml6YXRpb24iLCJ1c2VyIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJlbWFpbCBwcm9maWxlIn0.GnGiVju0yuzzrrfpM_6F37tNmJCfVpOynnmfIveoQBs",
"token_type": "bearer",
"not-before-policy": 0,
"session_state": "49d3d554-87f8-4b4f-9e93-d043611b70b2",
"scope": "email profile"
}
# ↓ "admin" トークンで /data/admin にアクセスしてみます。
* Trying 172.28.0.3...
* TCP_NODELAY set
* Connected to quarkus (172.28.0.3) port 8082 (#0)
> GET /data/admin HTTP/1.1
> Host: quarkus:8082
> User-Agent: curl/7.58.0
> Accept: */*
> Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJ1aW81bFppVU9tN19RZ1JISTRYOWpwRXFlVkN3THBfekZMOWxUMWJ5TkR3In0.eyJqdGkiOiIyNWRjOTI1Zi03YWNjLTRmMmMtOWI4ZS0zNjBkOWU0YmFiMzIiLCJleHAiOjE1NzIzMzQ5MDMsIm5iZiI6MCwiaWF0IjoxNTcyMzM0NjAzLCJpc3MiOiJodHRwOi8va2V5Y2xvYWs6ODE4MC9hdXRoL3JlYWxtcy9xdWFya3VzLXF1aWNrc3RhcnQiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiMDc3M2IxZTYtYWMwMy00Y2VjLTllNjctNTYwNzFhNzJlOTlkIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoicXVhcmt1cy1mcm9udCIsImF1dGhfdGltZSI6MCwic2Vzc2lvbl9zdGF0ZSI6IjQ5ZDNkNTU0LTg3ZjgtNGI0Zi05ZTkzLWQwNDM2MTFiNzBiMiIsImFjciI6IjEiLCJhbGxvd2VkLW9yaWdpbnMiOlsiaHR0cDovL3F1YXJrdXM6ODA4MiJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsib2ZmbGluZV9hY2Nlc3MiLCJhZG1pbiIsInVtYV9hdXRob3JpemF0aW9uIiwidXNlciJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoiZW1haWwgcHJvZmlsZSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwibmFtZSI6IkFybm8gQWRtaW4iLCJncm91cHMiOlsib2ZmbGluZV9hY2Nlc3MiLCJhZG1pbiIsInVtYV9hdXRob3JpemF0aW9uIiwidXNlciJdLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJhZG1pbiIsImdpdmVuX25hbWUiOiJBcm5vIiwiZmFtaWx5X25hbWUiOiJBZG1pbiIsImVtYWlsIjoiYWRtaW5AbG9jYWxob3N0In0.nUT8IU7jLNuN_00-yN8BccXAdYedJIyW-_dNY5ICPJR8bgWoie10TJpiwGdij6cOc5TsecSjuT6BvivqXgwbbOyTnh3Fs2ATXtbVFJI2WKfP4f2bbie_fBvRCD2wRfkpR5wGt3q9eceXwMOp0fh2ExvjJ3HgY_zhmerQpyZljeU6-YiF8JptiKRCrmD-USwmc0onQ50KKjWAFrhUYKcOAx7XTT2rHFspJrVPmbOhaSV6LCan4cqalbnfIgwoU8hVssvvW6DLdIEDjwEgrksmmKpT6nkl4sTMqFXVcbUA_UBhEopAEzts9DKEJZAGWXLqz-prt3wzFJm-Jws__2njog
>
< HTTP/1.1 200 OK
< Connection: keep-alive
< Content-Type: text/plain;charset=UTF-8
< Content-Length: 22
< Date: Tue, 29 Oct 2019 07:36:43 GMT
<
* Connection #0 to host quarkus left intact
data for admin "admin"
# ↑ アクセスできました!そしてユーザーが"admin" であることが確認できています。
# ↓ 続いて /data/user
* Trying 172.28.0.3...
* TCP_NODELAY set
* Connected to quarkus (172.28.0.3) port 8082 (#0)
> GET /data/user HTTP/1.1
> Host: quarkus:8082
> User-Agent: curl/7.58.0
> Accept: */*
> Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJ1aW81bFppVU9tN19RZ1JISTRYOWpwRXFlVkN3THBfekZMOWxUMWJ5TkR3In0.eyJqdGkiOiIyNWRjOTI1Zi03YWNjLTRmMmMtOWI4ZS0zNjBkOWU0YmFiMzIiLCJleHAiOjE1NzIzMzQ5MDMsIm5iZiI6MCwiaWF0IjoxNTcyMzM0NjAzLCJpc3MiOiJodHRwOi8va2V5Y2xvYWs6ODE4MC9hdXRoL3JlYWxtcy9xdWFya3VzLXF1aWNrc3RhcnQiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiMDc3M2IxZTYtYWMwMy00Y2VjLTllNjctNTYwNzFhNzJlOTlkIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoicXVhcmt1cy1mcm9udCIsImF1dGhfdGltZSI6MCwic2Vzc2lvbl9zdGF0ZSI6IjQ5ZDNkNTU0LTg3ZjgtNGI0Zi05ZTkzLWQwNDM2MTFiNzBiMiIsImFjciI6IjEiLCJhbGxvd2VkLW9yaWdpbnMiOlsiaHR0cDovL3F1YXJrdXM6ODA4MiJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsib2ZmbGluZV9hY2Nlc3MiLCJhZG1pbiIsInVtYV9hdXRob3JpemF0aW9uIiwidXNlciJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoiZW1haWwgcHJvZmlsZSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwibmFtZSI6IkFybm8gQWRtaW4iLCJncm91cHMiOlsib2ZmbGluZV9hY2Nlc3MiLCJhZG1pbiIsInVtYV9hdXRob3JpemF0aW9uIiwidXNlciJdLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJhZG1pbiIsImdpdmVuX25hbWUiOiJBcm5vIiwiZmFtaWx5X25hbWUiOiJBZG1pbiIsImVtYWlsIjoiYWRtaW5AbG9jYWxob3N0In0.nUT8IU7jLNuN_00-yN8BccXAdYedJIyW-_dNY5ICPJR8bgWoie10TJpiwGdij6cOc5TsecSjuT6BvivqXgwbbOyTnh3Fs2ATXtbVFJI2WKfP4f2bbie_fBvRCD2wRfkpR5wGt3q9eceXwMOp0fh2ExvjJ3HgY_zhmerQpyZljeU6-YiF8JptiKRCrmD-USwmc0onQ50KKjWAFrhUYKcOAx7XTT2rHFspJrVPmbOhaSV6LCan4cqalbnfIgwoU8hVssvvW6DLdIEDjwEgrksmmKpT6nkl4sTMqFXVcbUA_UBhEopAEzts9DKEJZAGWXLqz-prt3wzFJm-Jws__2njog
>
< HTTP/1.1 200 OK
< Connection: keep-alive
< Content-Type: text/plain;charset=UTF-8
< Content-Length: 21
< Date: Tue, 29 Oct 2019 07:36:43 GMT
<
* Connection #0 to host quarkus left intact
data for user "admin"
# ↑ こちらもアクセスできました!ユーザーが "admin" であることが認識できています。
うまく動いているようですね!
7. Quarkus の実装を確認
デモで無事に Quarkus と KeyCloak の連動が出来ていることが確認できましたが実装はどうなっているのでしょう?
実は非常にコード量が少ないので以下にこのデモAPIのプログラムを全文、載せます。
package com.github.thomasdarimont.keycloak;
import org.eclipse.microprofile.jwt.Claim;
import org.eclipse.microprofile.jwt.Claims;
import javax.annotation.security.RolesAllowed;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.json.Json;
import javax.json.JsonString;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import java.util.Optional;
/**
* Simple REST Resource that consumes information provided by a JWT token.
*
* Note that {@code RequestScoped} is explicitly needed here, since Quarkus changed the default
* scope for JAX-RS Resources to be {@code ApplicationScoped}.
*
* See: https://github.com/quarkusio/quarkus/issues/1710
*/
@Path("/data")
@RequestScoped
public class DataResource {
private static final JsonString ANOYNMOUS = Json.createValue("anonymous");
@Inject
@Claim("raw_token")
String rawToken;
@Inject
@Claim(standard = Claims.sub)
Optional<JsonString> subject;
@Inject
@Claim(standard = Claims.preferred_username)
Optional<JsonString> currentUsername;
@GET
@Path("/user")
@Produces(MediaType.TEXT_PLAIN)
@RolesAllowed({"user"})
public String userData() {
return "data for user " + currentUsername.orElse(ANOYNMOUS);
}
@GET
@Path("/admin")
@Produces(MediaType.TEXT_PLAIN)
@RolesAllowed({"admin"})
public String adminData() {
return "data for admin " + currentUsername.orElse(ANOYNMOUS);
}
}
AOP というか JavaEE(?) のアノテーション + Injection が効いててとんでもなく簡単にロール制御が実現できていることが分かります。
メソッド内のコードではロールやログインに関わる記述はせずに、呼び出されたときのコードだけを書いていることが分かります。
ロール制御のアノテーションも @RolesAllowed
だけで済んでいます。
あとは、KeyCloak を参照する設定を application.properties
に記述するだけです。
これで Quarkus の SSO 対応は完了、です。
う~ん、非常に簡単というか、さすがですね。。。これは参ったなぁ。。。SSO 必要なマイクロサービスは Quarkus でOKじゃないか。。。
まとめ
Quarkus + KeyCloak のデモで API 側の SSO 対応が圧巻の簡潔さで連携取れてしまうことが確認できました。
アノテーションでどうにかする文化はやっぱり JavaEE すごいなぁ。。。
また、GraalVMでネイティブ化 + Cloud Run に挑戦された方も発見しました!
Cloud Run + Cloud SQL の環境でネイティブ化された Quarkus + Hibernate のサービスが動いてるなんて、胸熱・・・だし、MicroProfile 対応なので Swagger ドキュメントも出せちゃうし、Zipkin も対応してるはずですよね?!ということで以下の記事!
いやいや、周辺技術をいろいろ調べだすと発散してしまうので・・・本日は以上といたします!
追記: Github に上げました。
上記の修正を施したquarkus-keycloak-demoを githubにて公開いたしました。(forkして修正しただけだけど・・・)