本記事では、KeycloakでOIDCを使いシングルサインオンを実行してみようと思います。
今回は以前紹介した認可コードフロー以外の以下3つのフローでシングルサインオンを実現してみようと思います。
- インプリシットフロー
- リソースオーナーパスワードクレデンシャル
- クライアントクレデンシャル
インプリシットフローとは
インプリシットフローは、パブリッククライアント(クライアントID・クライアントシークレットを安全に保持できないクライアント)向けの認可フローです。
このフローは認可コードフローと異なり、認可エンドポイントからアクセストークンが付与されたリダイレクトURIをレスポンスとして渡されます。RPはそのURIにアクセスしたのちIDトークンを検証して認証を実施します。
このフローではアクセストークン取得後にサーバサイドとやり取りすることを想定しないため、GETパラメータやPOSTパラメータを連携せずに、ハッシュフラグメントを用いて連携しています。
# 認可コードフローのリダイレクトURI
HTTP/1.1 302 Found
Server: nginx/1.18.0 (Ubuntu)
Date: Tue, 23 Jan 2024 07:04:38 GMT
Content-Length: 0
Connection: keep-alive
Cache-Control: no-store, must-revalidate, max-age=0
Set-Cookie: KEYCLOAK_LOCALE=; Version=1; Comment=Expiring cookie; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Max-Age=0; Path=/realms/myrealm/; HttpOnly
KC_RESTART=; Version=1; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Max-Age=0; Path=/realms/myrealm/; HttpOnly
KEYCLOAK_REMEMBER_ME=; Version=1; Comment=Expiring cookie; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Max-Age=0; Path=/realms/myrealm/; HttpOnly
Content-Security-Policy: frame-src 'self'; frame-ancestors 'self'; object-src 'none';
↓ パラメータとしてサーバに連携
Location: https://app.kanta-k.com/private/callback?state=MrTxfdlMiYuS5Y_MAwB0NyMlBdU&session_state=d4e4fde9-6067-43ba-ba93-e3c450187677&iss=https%3A%2F%2Fsso.kanta-k.com%2Frealms%2Fmyrealm&code=8dc2c104-8eca-41cd-916c-2d36bee01b13.d4e4fde9-6067-43ba-ba93-e3c450187677.5db7d057-ea84-4b9d-a668-cfe671a18fd9
# インプリシットフローのリダイレクトURI
HTTP/1.1 302 Found
Server: nginx/1.18.0 (Ubuntu)
Date: Fri, 26 Jan 2024 11:12:19 GMT
Content-Length: 0
Connection: keep-alive
Cache-Control: no-store, must-revalidate, max-age=0
Set-Cookie: KEYCLOAK_LOCALE=; Version=1; Comment=Expiring cookie; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Max-Age=0; Path=/realms/myrealm/; HttpOnly
Set-Cookie: KC_RESTART=; Version=1; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Max-Age=0; Path=/realms/myrealm/; HttpOnly
Set-Cookie: KC_AUTH_STATE=; Version=1; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Max-Age=0; Path=/realms/myrealm/
Content-Security-Policy: frame-src 'self'; frame-ancestors 'self'; object-src 'none';
↓ ハッシュフラグメントとしてブラウザ内で管理
Location: https://app.kanta-k.com/private/callback#state=g63-cttVVCUEwu1YBuuEl84HxcU&session_state=af46f2a0-79ff-4663-8fa7-f50ea71e7a52&iss=https%3A%2F%2Fsso.kanta-k.com%2Frealms%2Fmyrealm&id_token=eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJMM0w0ZDF6SnFmSHhhc1VKdnZ6VUVVaDF0MFhDUVNoZkdYM2lCX0NDOUFrIn0.eyJleHAiOjE3MDYyNjg0MzksImlhdCI6MTcwNjI2NzUzOSwiYXV0aF90aW1lIjoxNzA2MjY3NTM5LCJqdGkiOiJhM2E5NmQyMC03N2MxLTQxMGUtYWRhNC0yM2E4M2YyYzlhOGUiLCJpc3MiOiJodHRwczovL3Nzby5rYW50YS1rLmNvbS9yZWFsbXMvbXlyZWFsbSIsImF1ZCI6ImFwYWNoZTI0Iiwic3ViIjoiNWI1YWU2NTEtODQ1Ni00MWQwLWIyMjItZGE5ZDgxN2Y5YWQ3IiwidHlwIjoiSUQiLCJhenAiOiJhcGFjaGUyNCIsIm5vbmNlIjoiZ0lTaHFaSS1zSk9HMU5qVHY4eWJNd3NlTk1yY3lreVkxVGNoSC1OMnIxayIsInNlc3Npb25fc3RhdGUiOiJhZjQ2ZjJhMC03OWZmLTQ2NjMtOGZhNy1mNTBlYTcxZTdhNTIiLCJhdF9oYXNoIjoiVEZVd2Rfem9sdVJJWXV3T0FpTDBRQSIsImFjciI6IjEiLCJzX2hhc2giOiJ5V3ZnN3Jub212YS1BR05sNmlzSFB3Iiwic2lkIjoiYWY0NmYyYTAtNzlmZi00NjYzLThmYTctZjUwZWE3MWU3YTUyIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJrYW50YS1rIn0.LDbxqz0ywzw11LX98lniVEJfQsZ1tVWb34Fy1dFCZ4oWqkd7NrP7x2p_-1LVfcHWJTsEkg0tFUySp4lkWyM5AK9HBWXA1743AiWUVvIVjA_fxL2a18KMEBhGeCh7ildOgJi4riGZAq0xuw0FuofwvsS-zjOwD89ar0ElufrRyu_2tJT2P-iFBSyEP1ex4y5earS4LpYD6CWzuyIma3FrTrAtDdRK7AfcZMWcwH56DKXk9ebPdlfwTsh9nIxSTwhPmf8DJzH_jI2JXcO0wz--4bO7kwdVcByazjOQigUNGKRtSI2J5vB0XZ70eZlWhViT4vZaGNrv6DIih-ABPzy_EA&access_token=eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJMM0w0ZDF6SnFmSHhhc1VKdnZ6VUVVaDF0MFhDUVNoZkdYM2lCX0NDOUFrIn0.eyJleHAiOjE3MDYyNjg0MzksImlhdCI6MTcwNjI2NzUzOSwiYXV0aF90aW1lIjoxNzA2MjY3NTM5LCJqdGkiOiI3NzNmMTk3MS0yNGYwLTQyMzctOGQ0Yy0zMzM2YjJkNjI3OTQiLCJpc3MiOiJodHRwczovL3Nzby5rYW50YS1rLmNvbS9yZWFsbXMvbXlyZWFsbSIsImF1ZCI6ImFjY291bnQiLCJzdWIiOiI1YjVhZTY1MS04NDU2LTQxZDAtYjIyMi1kYTlkODE3ZjlhZDciLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJhcGFjaGUyNCIsIm5vbmNlIjoiZ0lTaHFaSS1zSk9HMU5qVHY4eWJNd3NlTk1yY3lreVkxVGNoSC1OMnIxayIsInNlc3Npb25fc3RhdGUiOiJhZjQ2ZjJhMC03OWZmLTQ2NjMtOGZhNy1mNTBlYTcxZTdhNTIiLCJhY3IiOiIxIiwiYWxsb3dlZC1vcmlnaW5zIjpbIiJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsiZGVmYXVsdC1yb2xlcy1teXJlYWxtIiwib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoib3BlbmlkIGVtYWlsIHByb2ZpbGUiLCJzaWQiOiJhZjQ2ZjJhMC03OWZmLTQ2NjMtOGZhNy1mNTBlYTcxZTdhNTIiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInByZWZlcnJlZF91c2VybmFtZSI6ImthbnRhLWsifQ.YDJUF7-Zbmrj7vEtwGDMG1WwiHMti0lKKeXlTCDepFYyqUYjwezHLfTkYd2toUHU5aRe6QjzJOcqSLGSs6IQW_q1xM40baKO-m59PyDCKMGvBLejxz2bUPDtuhcbgkbEnaPcQGcLRz2K3ADqGBRnhJc7NBGg8j9bzpHJHb_v8q5g0DZi9XGoV9wDephe7UqG5o7RFzA8cS0fykbfcM6TpM3zI7TdwoKqQVA4lc-ihvvGibXSqOICtitP0EJZZcnMOBedHoYjPWueFmrTvitgzCcNg12Wesex1RGMUUMop-dvHrRBPt81pQ03A0rgdco8cmot6h5st1tLEhXlhv6aZQ&token_type=Bearer&expires_in=900
Referrer-Policy: no-referrer
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-Robots-Tag: none
X-XSS-Protection: 1; mode=block
以下は具体的なシーケンス図となります。
また、Keycloakではかつてインプリシットフローでリフレッシュトークンを発行できたのですが、セキュリティの観点からKeycloak v12.x以降はリフレッシュトークン発行が無効となりました。1
構築手順
インプリシットフローの構築手順について紹介します。
構築に関しては、前の記事で作成した開発環境をそのまま使うことにします。
RP側の設定
こちらは、公式リファレンスに記載されているように、インプリシットフローを実現するためのトークンに切り替えておきます。
https://github.com/OpenIDC/mod_auth_openidc/blob/master/auth_openidc.conf#L253
OIDCResponseType "id_token token"
OP側の設定
こちらについては、Keycloak側にインプリシットフローを許可するように設定するだけです。
動かしてみたが...
しかし、動かしてみると502エラーが発生してしまう動きません...。
502エラーということはサーバ側で何かしら不具合が生じているということなので、ログを確認してみることにしました。
まず、起動させているKeycloak側のログを確認したところ、何も表示されていませんでした。
ということは、Keycloakにまで通信が到達していなかったのかもしれないので、手前のApahceを確認してみます。
tail -f /var/log/apache2/error.log
何も反応がありませんでした。
次に、nginxでエラーを確認してみました。
tail -f /var/log/nginx/error.log
2024/01/26 19:11:35 [error] 314#314: *718 upstream sent too big header while reading response header from upstream, client: 127.0.0.1, server: sso.kanta-k.com, request: "POST /realms/myrealm/login-actions/authenticate?session_code=UIXP9_PlekPIbiRXQgejVb8Vue5MbIdYujBRgydblO4&execution=69a2dcda-6e81-4ee3-8feb-8c7a2d28ab07&client_id=apache24&tab_id=SdhLI6ptb1c HTTP/1.1", upstream: "http://127.0.0.1:8080/realms/myrealm/login-actions/authenticate?session_code=UIXP9_PlekPIbiRXQgejVb8Vue5MbIdYujBRgydblO4&execution=69a2dcda-6e81-4ee3-8feb-8c7a2d28ab07&client_id=apache24&tab_id=SdhLI6ptb1c", host: "sso.kanta-k.com"
ログにエラーが出てきていますね。
とりあえず、エラー文から読み解けることとしては、ヘッダーのデータサイズが大きすぎてリクエストを送信することができないという内容でした。
こちらの対処法に関して調べてみたところ、nginxの設定ファイル側でヘッダーサイズを大きくすることで解消できそうです。2
# プロキシーバッファーを増やす
proxy_buffers 8 16k;
proxy_buffer_size 32k;
上記の設定を適用してnginxを再起動していざ実行してみたところ...。
無事にページにアクセスできました!
リソースオーナーパスワードクレデンシャルとは
アプリケーション利用者がIDとパスワードを入力してレスポンスを送信してデータ
ダイレクトグラントとも呼ばれます。
リソースオーナー(アプリユーザ)がIDとパスワードを入力して、入力された情報をKeycloak側に渡すことで認証を行い、アクセストークンが付与されたレスポンスを返す処理となります。
※本フローはクライアントにIDとパスワードを入力するため、IDプロバイダによる認証プロトコルとしては定義されていません。そのため、本フローはOIDCには含まれません。
(Keycloakは本フローに対応していますので利用できます)
クライアントクレデンシャルとは
クライアントがKeycloakにクライアント情報をリクエストヘッダーを介して渡して、クライアントIDとクライアントシークレットが正しければアクセストークンを発行するフローです。
このグラントタイプではアプリからKeycloakへの一度のリクエストだけでアクセストークンを取得できます。
具体的には、以下のようなシーケンスとなります。
クライアント側は保有するクライアントIDとクライアントシークレットをIDプロバイダ側にBasic認証で渡すことで、IDプロバイダ側が正しいクライアントからリクエストが届いたと判断してアクセストークンを発行します。
各フローの中でも最もシンプルなフローです。このフローは認可機能のみであり、認証を行っていません。つまり、クライアントを利用するユーザに依存せずに直接プロバイダからデータを取得したいといったシチュエーションがあるときには、ユーザを認証するプロセスを経ずにアクセストークンを取得するフローとしてこのクライアントクレデンシャルが存在しています。
上記の特徴から、クライアントがIDプロバイダと同じサーバ内にある時に用いるのが通常の利用方法となっています。
構築手順
Keycloakでリソースオーナーパスワードクレデンシャルやクライアントクレデンシャルを実行するときは、以下のように設定します。
リクエスト送信(リソースオーナーパスワードクレデンシャル)
リソースオーナパスワードクレデンシャルは、フローを見ていただけるとわかる通りクライアントからユーザIDとパスワードと直接通信に付与してリクエストを送信することでアクセストークンを取得できる。そのため、認可コードフローやインプリシットフローと異なりRelyingPartyを必要としません。
そのため、今回のアクセストークン取得処理はcurlコマンドで実行してみます。
以下のコマンドを入力します。
curl -k --header "Authorization: Basic YXBhY2hlMjQ6RzdQUlFiYWxkbzdsMTdBTjRMczBmRGZqMmpjTTlvVk4=" --data "username=kanta-k&password=test&grant_type=password&scope=openid" https://sso.kanta-k.com/realms/myrealm/protocol/openid-connect/token
上記コマンドではアクセストークンを張り出すURIに対してリクエストを実行しています。
この際、--data
オプションで指定されているgrant_type=password
は、認証フローの種類をリソースオーナーパスワードクレデンシャルに指定しています。
また、ヘッダー部分でクライアント情報をベーシック認証として受け渡し、データとしてユーザ名とパスワードを入れ込んでします。
上記コマンドを実行した結果を以下に示します。
{"access_token":"eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJMM0w0ZDF6SnFmSHhhc1VKdnZ6VUVVaDF0MFhDUVNoZkdYM2lCX0NDOUFrIn0.eyJleHAiOjE3MDYyNjEwOTEsImlhdCI6MTcwNjI2MDc5MSwianRpIjoiYzFhMzk3ZjktYTZlYS00ZDZiLTkxNTktOWEwMzgxMDUyNjUxIiwiaXNzIjoiaHR0cHM6Ly9zc28ua2FudGEtay5jb20vcmVhbG1zL215cmVhbG0iLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiNWI1YWU2NTEtODQ1Ni00MWQwLWIyMjItZGE5ZDgxN2Y5YWQ3IiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiYXBhY2hlMjQiLCJzZXNzaW9uX3N0YXRlIjoiY2RmYmYwMzEtMzI1Yi00YTQ0LTk5NDgtNzUwNzY2Nzg3MjhlIiwiYWNyIjoiMSIsImFsbG93ZWQtb3JpZ2lucyI6WyJodHRwOi8vbG9jYWxob3N0OjgxIiwiaHR0cHM6Ly9hcHAua2FudGEtay5jb20iLCJodHRwOi8vYXBwLmthbnRhLWsuY29tIl0sInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJkZWZhdWx0LXJvbGVzLW15cmVhbG0iLCJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJvcGVuaWQgZW1haWwgcHJvZmlsZSIsInNpZCI6ImNkZmJmMDMxLTMyNWItNGE0NC05OTQ4LTc1MDc2Njc4NzI4ZSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwicHJlZmVycmVkX3VzZXJuYW1lIjoia2FudGEtayJ9.WchguCnv5KcWjC0GPlSZ1NVIahSp7YcrcXK-2xJ8O1NauqmQPRfVmtG3xitUPelAPq05iaqDsHkbiczDAgrrB7n_47CMWijoTED2GAdZyZxypM7hqmDyjXqP29TaumDhbe4EYwfXvLoc8fUJSyWLPCruKxT_UEBiEsJA5YoL60IVIfIvK79yDVsBL1dC-02Fqf4DrWZwblB85P7M5MIrZGaRTSyQIBu5W1XqysRWT-ZBn80-DqQK0kPZ4fK18MAjMXQUmSNf6z_xsTXaWbHS_kotvanf-JbZv8lpgJbQQ16vbKuh0Z71fbEKNcw8kscJBhW56IaNO1mEWTA8X0KMmg","expires_in":300,"refresh_expires_in":1800,"refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI2ZTVlM2FmNC05YWE1LTQzZTctYWI1Zi0zZTdmMGQ4MWZmZTYifQ.eyJleHAiOjE3MDYyNjI1OTEsImlhdCI6MTcwNjI2MDc5MSwianRpIjoiYmFmNjQ3MjktMmY5NS00ODVlLTk2NzMtMTMyNjdjZmIzODlkIiwiaXNzIjoiaHR0cHM6Ly9zc28ua2FudGEtay5jb20vcmVhbG1zL215cmVhbG0iLCJhdWQiOiJodHRwczovL3Nzby5rYW50YS1rLmNvbS9yZWFsbXMvbXlyZWFsbSIsInN1YiI6IjViNWFlNjUxLTg0NTYtNDFkMC1iMjIyLWRhOWQ4MTdmOWFkNyIsInR5cCI6IlJlZnJlc2giLCJhenAiOiJhcGFjaGUyNCIsInNlc3Npb25fc3RhdGUiOiJjZGZiZjAzMS0zMjViLTRhNDQtOTk0OC03NTA3NjY3ODcyOGUiLCJzY29wZSI6Im9wZW5pZCBlbWFpbCBwcm9maWxlIiwic2lkIjoiY2RmYmYwMzEtMzI1Yi00YTQ0LTk5NDgtNzUwNzY2Nzg3MjhlIn0.O-Nf_iSKfqDMHLKUeEK9JoOieTwndcYmxMAwKBdPX34","token_type":"Bearer","id_token":"eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJMM0w0ZDF6SnFmSHhhc1VKdnZ6VUVVaDF0MFhDUVNoZkdYM2lCX0NDOUFrIn0.eyJleHAiOjE3MDYyNjEwOTEsImlhdCI6MTcwNjI2MDc5MSwiYXV0aF90aW1lIjowLCJqdGkiOiI2OTRlNzZmOS02ZTFmLTQ2YjctYWYwMS03MjJkZTZiNjcwNDciLCJpc3MiOiJodHRwczovL3Nzby5rYW50YS1rLmNvbS9yZWFsbXMvbXlyZWFsbSIsImF1ZCI6ImFwYWNoZTI0Iiwic3ViIjoiNWI1YWU2NTEtODQ1Ni00MWQwLWIyMjItZGE5ZDgxN2Y5YWQ3IiwidHlwIjoiSUQiLCJhenAiOiJhcGFjaGUyNCIsInNlc3Npb25fc3RhdGUiOiJjZGZiZjAzMS0zMjViLTRhNDQtOTk0OC03NTA3NjY3ODcyOGUiLCJhdF9oYXNoIjoiMkJnMlRmc2hPMWRxNDRCYUhnX2dWdyIsImFjciI6IjEiLCJzaWQiOiJjZGZiZjAzMS0zMjViLTRhNDQtOTk0OC03NTA3NjY3ODcyOGUiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInByZWZlcnJlZF91c2VybmFtZSI6ImthbnRhLWsifQ.ck0YFaIfvPMDnYOTYuNTmV1ajeNxT5U1ftuPLV_P6mQ0STTIQqKa94Ce8q9N8QjpdbXDJZW6OFIXfwrK-UE1_i5Q8NMxfbghfVOionjd7G02rEX__oFv20gG-fYmL_O_IUcTP813f5XRfPEk7ljUgGzP0Clxp-iB09d2xqm1vHgzAzlP5v6p21lfvenxOjpYxUsiKtI8zcS_IN14wd_IXQ_hjKI1yZOP6eWL7ps_g0TEkTqXtr4Wcdk2fpkgBY2X1YfwGKrbCEVjlyxgxZUMDZfTiLFAJobAYHGmyGQDbkdx1io7JZgHJG6WN7vEYEMbkar5_30KpF3KDw7pRxAVyw","not-before-policy":0,"session_state":"cdfbf031-325b-4a44-9948-75076678728e","scope":"openid email profile"}
このままではわかりにくいので整形します。
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJMM0w0ZDF6SnFmSHhhc1VKdnZ6VUVVaDF0MFhDUVNoZkdYM2lCX0NDOUFrIn0.eyJleHAiOjE3MDYyNjEwOTEsImlhdCI6MTcwNjI2MDc5MSwianRpIjoiYzFhMzk3ZjktYTZlYS00ZDZiLTkxNTktOWEwMzgxMDUyNjUxIiwiaXNzIjoiaHR0cHM6Ly9zc28ua2FudGEtay5jb20vcmVhbG1zL215cmVhbG0iLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiNWI1YWU2NTEtODQ1Ni00MWQwLWIyMjItZGE5ZDgxN2Y5YWQ3IiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiYXBhY2hlMjQiLCJzZXNzaW9uX3N0YXRlIjoiY2RmYmYwMzEtMzI1Yi00YTQ0LTk5NDgtNzUwNzY2Nzg3MjhlIiwiYWNyIjoiMSIsImFsbG93ZWQtb3JpZ2lucyI6WyJodHRwOi8vbG9jYWxob3N0OjgxIiwiaHR0cHM6Ly9hcHAua2FudGEtay5jb20iLCJodHRwOi8vYXBwLmthbnRhLWsuY29tIl0sInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJkZWZhdWx0LXJvbGVzLW15cmVhbG0iLCJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJvcGVuaWQgZW1haWwgcHJvZmlsZSIsInNpZCI6ImNkZmJmMDMxLTMyNWItNGE0NC05OTQ4LTc1MDc2Njc4NzI4ZSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwicHJlZmVycmVkX3VzZXJuYW1lIjoia2FudGEtayJ9.WchguCnv5KcWjC0GPlSZ1NVIahSp7YcrcXK-2xJ8O1NauqmQPRfVmtG3xitUPelAPq05iaqDsHkbiczDAgrrB7n_47CMWijoTED2GAdZyZxypM7hqmDyjXqP29TaumDhbe4EYwfXvLoc8fUJSyWLPCruKxT_UEBiEsJA5YoL60IVIfIvK79yDVsBL1dC-02Fqf4DrWZwblB85P7M5MIrZGaRTSyQIBu5W1XqysRWT-ZBn80-DqQK0kPZ4fK18MAjMXQUmSNf6z_xsTXaWbHS_kotvanf-JbZv8lpgJbQQ16vbKuh0Z71fbEKNcw8kscJBhW56IaNO1mEWTA8X0KMmg",
"expires_in": 300,
"refresh_expires_in": 1800,
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI2ZTVlM2FmNC05YWE1LTQzZTctYWI1Zi0zZTdmMGQ4MWZmZTYifQ.eyJleHAiOjE3MDYyNjI1OTEsImlhdCI6MTcwNjI2MDc5MSwianRpIjoiYmFmNjQ3MjktMmY5NS00ODVlLTk2NzMtMTMyNjdjZmIzODlkIiwiaXNzIjoiaHR0cHM6Ly9zc28ua2FudGEtay5jb20vcmVhbG1zL215cmVhbG0iLCJhdWQiOiJodHRwczovL3Nzby5rYW50YS1rLmNvbS9yZWFsbXMvbXlyZWFsbSIsInN1YiI6IjViNWFlNjUxLTg0NTYtNDFkMC1iMjIyLWRhOWQ4MTdmOWFkNyIsInR5cCI6IlJlZnJlc2giLCJhenAiOiJhcGFjaGUyNCIsInNlc3Npb25fc3RhdGUiOiJjZGZiZjAzMS0zMjViLTRhNDQtOTk0OC03NTA3NjY3ODcyOGUiLCJzY29wZSI6Im9wZW5pZCBlbWFpbCBwcm9maWxlIiwic2lkIjoiY2RmYmYwMzEtMzI1Yi00YTQ0LTk5NDgtNzUwNzY2Nzg3MjhlIn0.O-Nf_iSKfqDMHLKUeEK9JoOieTwndcYmxMAwKBdPX34",
"token_type": "Bearer",
"id_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJMM0w0ZDF6SnFmSHhhc1VKdnZ6VUVVaDF0MFhDUVNoZkdYM2lCX0NDOUFrIn0.eyJleHAiOjE3MDYyNjEwOTEsImlhdCI6MTcwNjI2MDc5MSwiYXV0aF90aW1lIjowLCJqdGkiOiI2OTRlNzZmOS02ZTFmLTQ2YjctYWYwMS03MjJkZTZiNjcwNDciLCJpc3MiOiJodHRwczovL3Nzby5rYW50YS1rLmNvbS9yZWFsbXMvbXlyZWFsbSIsImF1ZCI6ImFwYWNoZTI0Iiwic3ViIjoiNWI1YWU2NTEtODQ1Ni00MWQwLWIyMjItZGE5ZDgxN2Y5YWQ3IiwidHlwIjoiSUQiLCJhenAiOiJhcGFjaGUyNCIsInNlc3Npb25fc3RhdGUiOiJjZGZiZjAzMS0zMjViLTRhNDQtOTk0OC03NTA3NjY3ODcyOGUiLCJhdF9oYXNoIjoiMkJnMlRmc2hPMWRxNDRCYUhnX2dWdyIsImFjciI6IjEiLCJzaWQiOiJjZGZiZjAzMS0zMjViLTRhNDQtOTk0OC03NTA3NjY3ODcyOGUiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInByZWZlcnJlZF91c2VybmFtZSI6ImthbnRhLWsifQ.ck0YFaIfvPMDnYOTYuNTmV1ajeNxT5U1ftuPLV_P6mQ0STTIQqKa94Ce8q9N8QjpdbXDJZW6OFIXfwrK-UE1_i5Q8NMxfbghfVOionjd7G02rEX__oFv20gG-fYmL_O_IUcTP813f5XRfPEk7ljUgGzP0Clxp-iB09d2xqm1vHgzAzlP5v6p21lfvenxOjpYxUsiKtI8zcS_IN14wd_IXQ_hjKI1yZOP6eWL7ps_g0TEkTqXtr4Wcdk2fpkgBY2X1YfwGKrbCEVjlyxgxZUMDZfTiLFAJobAYHGmyGQDbkdx1io7JZgHJG6WN7vEYEMbkar5_30KpF3KDw7pRxAVyw",
"not-before-policy": 0,
"session_state": "cdfbf031-325b-4a44-9948-75076678728e",
"scope": "openid email profile"
}
無事トークンを取得できていることを確認できました。
リクエスト送信(クライアントクレデンシャル)
クライアントクレデンシャルは、フローを見ていただけるとわかる通り正しいクライアントからの要求であればほかの処理を行うことなくアクセストークンを取得できます。
そのため、今回のアクセストークン取得処理はcurlコマンドで実行してみます。
以下のコマンドを入力します。
curl -k --header "Authorization: Basic YXBhY2hlMjQ6RzdQUlFiYWxkbzdsMTdBTjRMczBmRGZqMmpjTTlvVk4=" --data "grant_type=client_credentials&scope=openid" https://sso.kanta-k.com/realms/myrealm/protocol/openid-connect/token
上記コマンドではアクセストークンを張り出すURIに対してリクエストを実行しています。
この際、--data
オプションで指定されているgrant_type=password
は、認証フローの種類をクライアントクレデンシャルに指定しています。
また、ヘッダー部分でクライアント情報をベーシック認証として受け渡し、データとしてユーザ名とパスワードを入れ込んでいます。
上記コマンドを実行した結果を以下に示します。
{"access_token":"eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJMM0w0ZDF6SnFmSHhhc1VKdnZ6VUVVaDF0MFhDUVNoZkdYM2lCX0NDOUFrIn0.eyJleHAiOjE3MDYyNjIwMjcsImlhdCI6MTcwNjI2MT...","expires_in":300,"refresh_expires_in":0,"token_type":"Bearer","id_token":"eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJMM0w0ZDF6SnFmSHhhc1VKdnZ6VUVVaDF0MFhDUVNoZkdYM2lCX0NDOUFrIn0.eyJleHAiOjE3MDYyNjIwMjcsImlhdCI...","not-before-policy":0,"scope":"openid email profile"}
このままではわかりにくいので整形します。
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJMM0w0ZDF6SnFmSHhhc1VKdnZ6VUVVaDF0MFhDUVNoZkdYM2lCX0NDOUFrIn0.eyJleHAiOjE3MDYyNjIwMjcsImlhdCI6MTcwNjI2MT...",
"expires_in": 300,
"refresh_expires_in": 0,
"token_type": "Bearer",
"id_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJMM0w0ZDF6SnFmSHhhc1VKdnZ6VUVVaDF0MFhDUVNoZkdYM2lCX0NDOUFrIn0.eyJleHAiOjE3MDYyNjIwMjcsImlhdCI...",
"not-before-policy": 0,
"scope": "openid email profile"
}
こちらのレスポンスを見てみると、ほかのアクセストークン取得時のレスポンスとは異なりリフレッシュトークンが発行されていないことがわかります。
理由としては、Keycloak12.0.0以降ではクライアントクレデンシャルでリフレッシュトークンを発行しないようになっているためです。