Red Hat Single Sign-on(以下、Red Hat SSO)でトークンにクレームを追加してみます。
Red Hat SSOは、OSSのKeycloakをベースとした製品で、サポートを受けることができます。
やり方
トークンにクレームを追加するやり方は、ざっくり3つあります。
・プロトコルマッパー: クライアント(Webアプリ)単位でGUIで設定する
・クライアントスコープ: 複数のクライアント(Webアプリ単位)に共通設定でGUIで設定する
・スクリプトマッパー: JavaScriptを書いて、自由にトークンを設定(マッピング)する
プロトコルマッパーやクライアントスコープで用意されているマッピングで足りない場合は、
スクリプトマッパーでコーディングする形になると思います。
環境
・OpenJDK 8
・Red Hat SSO 7.6.1 (Keycloak 18.0.3)
(とりあえず、記事書いた時点の最新版を使っています)
Red Hat Single Sign-OnとKeycloakのバージョン対応表はこちらにあります。
JWT関係のツール
トークンの確認で少し便利になるjwt-cliを入れておきます。
環境的にコマンド入れるのが、できない or 面倒な場合は、以下のサイトでトークンの確認でもOKです。
まずは、素の状態トークンを確認してみる
Red Hat SSOをインストール(zip展開)して、
Red Hat SSOの管理ユーザも作っておきます。(便宜上、admin/adminで作ってます)
$ $SSO_HOME/bin/add-user-keycloak.sh -u admin -p admin
Added 'admin' to '/xxxx/standalone/configuration/keycloak-add-user.json', restart server to load user
環境変数の$SSO_HOMEは、zipを展開して作成したディレクトリーとします。
対象とするクライアントは、最初から設定されていて、クライアントの設定が[Direct Access Grants Enabled]が有効になっている admin-cliを利用します。
まずは、何も設定変更していない状態でトークンを取得します。
$ curl -s \
-d "client_id=admin-cli" \
-d "username=admin" \
-d "password=admin" \
-d "grant_type=password" \
-d "scope=openid" \
"http://localhost:8080/auth/realms/master/protocol/openid-connect/token" | jq
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJaNlRNcGRycnd3d25XYkZSZUFCRG1pdDUtam1jRURKcXg2YU9kdXBHLTZzIn0.eyJleHAiOjE2Njk2MjgxMDgsImlhdCI6MTY2OTYyODA0OCwianRpIjoiMTllMDViMzItYTI4ZS00OWRmLWFmOTctMTE5Y2I5NjQ3NGI2IiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL2F1dGgvcmVhbG1zL21hc3RlciIsInN1YiI6IjI0YTgxOTJiLThjMzktNDgzZi05ZDc5LWRjMGYzYjg0ZjFlYyIsInR5cCI6IkJlYXJlciIsImF6cCI6ImFkbWluLWNsaSIsInNlc3Npb25fc3RhdGUiOiJiYzM1MzRkZi05N2Y4LTRmYWUtYmRlYS1kYmE3NGRiODNlNmMiLCJhY3IiOiIxIiwic2NvcGUiOiJvcGVuaWQgcHJvZmlsZSBlbWFpbCIsInNpZCI6ImJjMzUzNGRmLTk3ZjgtNGZhZS1iZGVhLWRiYTc0ZGI4M2U2YyIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwicHJlZmVycmVkX3VzZXJuYW1lIjoiYWRtaW4ifQ.e_AITniwMZa7O28eKObZe5YSiu-2ZT1W_6nBYrb7KSyxIsHhRUOF6er9oqkcSbU90_F100NXGSfwDhdUNDSr_fr7DYiQvumQkTUv6aQQ1UnRMn0C9qmqo2-a-23yZ_vipkf61_uoyWK_U76STsKdV4EReRAPeWJru02KS101F4I-QG08gTNfxIXW1m4RNWL0tpHlVwAZCDxEgtf-W4_3cx8Ej7EmSDu4VTjPFFpCieLye3z-Zconl1gTks0n7a816X3_6m9eNKiEDlWWtmX1AgOTKUOqZDLgZltJJxSuBcVVQ7zdMPyfqD6j_1rSj5dNnUvFijptn8v0E-o42YCRBQ",
"expires_in": 60,
"refresh_expires_in": 1800,
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICIzYjBjOTNjNC02MmNhLTQ4MzEtYjUwOC0xZTEzZDVhOWEwNmIifQ.eyJleHAiOjE2Njk2Mjk4NDgsImlhdCI6MTY2OTYyODA0OCwianRpIjoiNDQyYjZlNDUtZDdmNC00OTY3LTllYTktOTgxM2FlZjg2NDZjIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL2F1dGgvcmVhbG1zL21hc3RlciIsImF1ZCI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9hdXRoL3JlYWxtcy9tYXN0ZXIiLCJzdWIiOiIyNGE4MTkyYi04YzM5LTQ4M2YtOWQ3OS1kYzBmM2I4NGYxZWMiLCJ0eXAiOiJSZWZyZXNoIiwiYXpwIjoiYWRtaW4tY2xpIiwic2Vzc2lvbl9zdGF0ZSI6ImJjMzUzNGRmLTk3ZjgtNGZhZS1iZGVhLWRiYTc0ZGI4M2U2YyIsInNjb3BlIjoib3BlbmlkIHByb2ZpbGUgZW1haWwiLCJzaWQiOiJiYzM1MzRkZi05N2Y4LTRmYWUtYmRlYS1kYmE3NGRiODNlNmMifQ.D6-YZxykFUUtVSKwbDnX1XKXnkpRExhCnpzv0wTfaBQ",
"token_type": "Bearer",
"id_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJaNlRNcGRycnd3d25XYkZSZUFCRG1pdDUtam1jRURKcXg2YU9kdXBHLTZzIn0.eyJleHAiOjE2Njk2MjgxMDgsImlhdCI6MTY2OTYyODA0OCwiYXV0aF90aW1lIjowLCJqdGkiOiJmYjliMjc5Yi1iYTNiLTRkOTQtOGJiNi1mYmEzYzk5MmE4YmUiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvYXV0aC9yZWFsbXMvbWFzdGVyIiwiYXVkIjoiYWRtaW4tY2xpIiwic3ViIjoiMjRhODE5MmItOGMzOS00ODNmLTlkNzktZGMwZjNiODRmMWVjIiwidHlwIjoiSUQiLCJhenAiOiJhZG1pbi1jbGkiLCJzZXNzaW9uX3N0YXRlIjoiYmMzNTM0ZGYtOTdmOC00ZmFlLWJkZWEtZGJhNzRkYjgzZTZjIiwiYXRfaGFzaCI6IkNPQ2pJWVFoN1JlOGw4WGIwSTJIN1EiLCJhY3IiOiIxIiwic2lkIjoiYmMzNTM0ZGYtOTdmOC00ZmFlLWJkZWEtZGJhNzRkYjgzZTZjIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJhZG1pbiJ9.l7zdcCWED9CnfjGNV_vFFQUHGOa9E5nxB0s9TLtHfKqTfrBJhRJF3HsR4dVFBR16COwn5RxXH2sGAWdoyX6pPoSx_t3i9zErfTn9ZFryevAxfiaKygDJ_Gddcrz589n-qJ5NS7RS5mgH0W4crSZDbkuLuFPFVKT4F4paqIXzwf4bZ7R2ZYtbI9Bz9bLd7k6iWjaolGs8KpiSIpgsL_y8RCuN_3v9VfiV6d4ukDUMKrLyKESAWxaRnYOq4ZEnFqjUdr7Khs-RjD1WwRhtg2EvPqHe8oF8i0UcljzrVP8VzMeLW66yV36E7nQcndbqs4JzYjamnDiBgsnXqIUt_yxk4g",
"not-before-policy": 0,
"session_state": "bc3534df-97f8-4fae-bdea-dba74db83e6c",
"scope": "openid profile email"
}
トークンの中身をデコードします。("access_token": "ey〜〜BQ" のey〜〜BQの文字列をコピペします。環境によって異なります)
$ jwt decode eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJaNlRNcGRycnd3d25XYkZSZUFCRG1pdDUtam1jRURKcXg2YU9kdXBHLTZzIn0.eyJleHAiOjE2Njk2MjgxMDgsImlhdCI6MTY2OTYyODA0OCwianRpIjoiMTllMDViMzItYTI4ZS00OWRmLWFmOTctMTE5Y2I5NjQ3NGI2IiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL2F1dGgvcmVhbG1zL21hc3RlciIsInN1YiI6IjI0YTgxOTJiLThjMzktNDgzZi05ZDc5LWRjMGYzYjg0ZjFlYyIsInR5cCI6IkJlYXJlciIsImF6cCI6ImFkbWluLWNsaSIsInNlc3Npb25fc3RhdGUiOiJiYzM1MzRkZi05N2Y4LTRmYWUtYmRlYS1kYmE3NGRiODNlNmMiLCJhY3IiOiIxIiwic2NvcGUiOiJvcGVuaWQgcHJvZmlsZSBlbWFpbCIsInNpZCI6ImJjMzUzNGRmLTk3ZjgtNGZhZS1iZGVhLWRiYTc0ZGI4M2U2YyIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwicHJlZmVycmVkX3VzZXJuYW1lIjoiYWRtaW4ifQ.e_AITniwMZa7O28eKObZe5YSiu-2ZT1W_6nBYrb7KSyxIsHhRUOF6er9oqkcSbU90_F100NXGSfwDhdUNDSr_fr7DYiQvumQkTUv6aQQ1UnRMn0C9qmqo2-a-23yZ_vipkf61_uoyWK_U76STsKdV4EReRAPeWJru02KS101F4I-QG08gTNfxIXW1m4RNWL0tpHlVwAZCDxEgtf-W4_3cx8Ej7EmSDu4VTjPFFpCieLye3z-Zconl1gTks0n7a816X3_6m9eNKiEDlWWtmX1AgOTKUOqZDLgZltJJxSuBcVVQ7zdMPyfqD6j_1rSj5dNnUvFijptn8v0E-o42YCRBQ
Token header
------------
{
"typ": "JWT",
"alg": "RS256",
"kid": "Z6TMpdrrwwwnWbFReABDmit5-jmcEDJqx6aOdupG-6s"
}
Token claims
------------
{
"acr": "1",
"azp": "admin-cli",
"email_verified": false,
"exp": 1669628108,
"iat": 1669628048,
"iss": "http://localhost:8080/auth/realms/master",
"jti": "19e05b32-a28e-49df-af97-119cb96474b6",
"preferred_username": "admin",
"scope": "openid profile email",
"session_state": "bc3534df-97f8-4fae-bdea-dba74db83e6c",
"sid": "bc3534df-97f8-4fae-bdea-dba74db83e6c",
"sub": "24a8192b-8c39-483f-9d79-dc0f3b84f1ec",
"typ": "Bearer"
}
Token claimsのところに、acrやazpなど色々出ています。
次は、ここのクレームを追加してみます。
クレームを追加してみる
一番簡単そうなプロトコルマッパーでやってみます。
今回は、OIDCトークンのクレームをいじりますが、SAMLアサーションのアサーションも設定できます。
まずは、プロトコルマッパーの画面を見てみます。
[Clients] -> [admin-cli] -> [Mappers]まで辿って、Createボタンを押します。
Create Protocol Mapperの画面に遷移して、Mapper Typeのプルダウンをみると、いっぱい表示されています。
これは、事前に用意されているプロトコルマッパーの一覧で、それぞれのプロトコルマッパーで出来ることが異なります。
アプリへのロール割当などでgroupのクレームが使われることがあるので、今回はGroup Membershipのプロトコルマッパーを使ってみます。
設定すると、以下のようにgroupクレームが追加されます。
{
"acr": "1",
"azp": "admin-cli",
"email_verified": false,
"exp": 1669642563,
"group": [
"/Agroup"
"/Bgroup"
],
"iat": 1669642503,
"iss": "http://localhost:8080/auth/realms/master",
"jti": "94e4b8be-f338-421a-b0e7-c2f2bb956c4f",
"preferred_username": "admin",
"scope": "openid profile email",
"session_state": "0ea24057-cb0e-4333-89b7-61a72d05b327",
"sid": "0ea24057-cb0e-4333-89b7-61a72d05b327",
"sub": "24a8192b-8c39-483f-9d79-dc0f3b84f1ec",
"typ": "Bearer"
}
とりあえず、グループが必要なので、AgroupとBgroupという名前でグループを2つ作っておきます。
そして、adminユーザにAgroupとBgroupをジョインさせます。
admin-cliのCreate Protocol Mapperの画面に戻って、Group Membershipを選択して、Nameをgroup、Token Claim Nameをgroupにします。
(Token Claim Nameで指定した値がクレーム名になります)
HTTPでクレームを確認しなくても、Client ScopesのタブのEvaluateでトークンを評価することができます。
Userのところに、adminを入れて、Evaluteを押します。その後に、[Generated Access Token]を押します。
[Generated Access Token]を押すと、トークンの内容が表示されます。
groupのクレームが追加されていることがわかります。
念の為、コマンドでも確認します。
まずは、トークンを取得します。
$ curl -s \
-d "client_id=admin-cli" \
-d "username=admin" \
-d "password=admin" \
-d "grant_type=password" \
-d "scope=openid" \
"http://localhost:8080/auth/realms/master/protocol/openid-connect/token" | jq
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJaNlRNcGRycnd3d25XYkZSZUFCRG1pdDUtam1jRURKcXg2YU9kdXBHLTZzIn0.eyJleHAiOjE2Njk2NDU0MTksImlhdCI6MTY2OTY0NTM1OSwianRpIjoiOWUyZjA2NGQtMTk1Yi00OTUzLWFiMDAtNGQ1MDJmOGU5ZmQ5IiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL2F1dGgvcmVhbG1zL21hc3RlciIsInN1YiI6IjI0YTgxOTJiLThjMzktNDgzZi05ZDc5LWRjMGYzYjg0ZjFlYyIsInR5cCI6IkJlYXJlciIsImF6cCI6ImFkbWluLWNsaSIsInNlc3Npb25fc3RhdGUiOiJiMGM5MWM4OS1iODIwLTRiMmQtYTU1YS03ZmY4MGNlODhlMjIiLCJhY3IiOiIxIiwic2NvcGUiOiJvcGVuaWQgcHJvZmlsZSBlbWFpbCIsInNpZCI6ImIwYzkxYzg5LWI4MjAtNGIyZC1hNTVhLTdmZjgwY2U4OGUyMiIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwicHJlZmVycmVkX3VzZXJuYW1lIjoiYWRtaW4iLCJncm91cCI6WyIvQWdyb3VwIiwiL0Jncm91cCJdfQ.WP1asd7M8uaUpr15k3-wxjWW6YTqna14B0KaB2E68ERHWmtfSOZiMaqn9mbYCS0aKTWJCBgkF189sJWTG5QVEU58j5xhzIZHr-0sR5BtkQdlJqgDTebTaMuKBHNDNVGfn_CQMNZ3M-kZQFFGhroqhzfRfmR5gTwYGxl1JgsWL0oX9MTDuZfW0RRK8S02TVwG4GSGrEBaYeOU4q_NtAoAPTJlMbkZSw5S9vNYh2ddkN8U27VB-c5QWljDZ3pczLG3JLHCCnXAfb9AJSr8mesu49IFMmSlQykvLoWoCfrb6U0k-F5me-U2yaTRtzBgLMidcOqJ4gkC4CiiAPlDsqTVxw",
"expires_in": 60,
"refresh_expires_in": 1800,
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICIzYjBjOTNjNC02MmNhLTQ4MzEtYjUwOC0xZTEzZDVhOWEwNmIifQ.eyJleHAiOjE2Njk2NDcxNTksImlhdCI6MTY2OTY0NTM1OSwianRpIjoiZWYyZjVkMDktYjhiYi00OTRlLWFjNjctNDRhN2Y3YTcxZWU3IiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL2F1dGgvcmVhbG1zL21hc3RlciIsImF1ZCI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9hdXRoL3JlYWxtcy9tYXN0ZXIiLCJzdWIiOiIyNGE4MTkyYi04YzM5LTQ4M2YtOWQ3OS1kYzBmM2I4NGYxZWMiLCJ0eXAiOiJSZWZyZXNoIiwiYXpwIjoiYWRtaW4tY2xpIiwic2Vzc2lvbl9zdGF0ZSI6ImIwYzkxYzg5LWI4MjAtNGIyZC1hNTVhLTdmZjgwY2U4OGUyMiIsInNjb3BlIjoib3BlbmlkIHByb2ZpbGUgZW1haWwiLCJzaWQiOiJiMGM5MWM4OS1iODIwLTRiMmQtYTU1YS03ZmY4MGNlODhlMjIifQ.yoiinWSYyk-o7uS3eRV6XDo-eXk4VFi8arrJlohvld4",
"token_type": "Bearer",
"id_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJaNlRNcGRycnd3d25XYkZSZUFCRG1pdDUtam1jRURKcXg2YU9kdXBHLTZzIn0.eyJleHAiOjE2Njk2NDU0MTksImlhdCI6MTY2OTY0NTM1OSwiYXV0aF90aW1lIjowLCJqdGkiOiIxMjkzOTVhOS1lMGM0LTQ0ZmMtYTNhZi03YjMzOGU0OGUyNmYiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvYXV0aC9yZWFsbXMvbWFzdGVyIiwiYXVkIjoiYWRtaW4tY2xpIiwic3ViIjoiMjRhODE5MmItOGMzOS00ODNmLTlkNzktZGMwZjNiODRmMWVjIiwidHlwIjoiSUQiLCJhenAiOiJhZG1pbi1jbGkiLCJzZXNzaW9uX3N0YXRlIjoiYjBjOTFjODktYjgyMC00YjJkLWE1NWEtN2ZmODBjZTg4ZTIyIiwiYXRfaGFzaCI6IlRrTkpndjlxRmQ4c1BpOVBaTnVHcGciLCJhY3IiOiIxIiwic2lkIjoiYjBjOTFjODktYjgyMC00YjJkLWE1NWEtN2ZmODBjZTg4ZTIyIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJhZG1pbiIsImdyb3VwIjpbIi9BZ3JvdXAiLCIvQmdyb3VwIl19.ADr8phw94sjoi3hcCNWda2uodNo3lXc8HNrz3kHinuu3yK3EW1BTRbi4N_LMRm35IFplVHfhsjx6-wyIkHQ2ffImKmZmMf3yVmbz9HD4TkUdHRF8jipM_hDRbiYTcnBFdfewU_OCwxdVm9VL0C3C5Om_3mbn2FPFiFh-Zqh5zH7YJ6F2jgWYp1AQA1ybGci5u7RQkW57hUzGOtotal0hOzUxMKfU-gTK8hvuq15CtjqpgPat4PMEzDjVwHQl65zVECDgjZemp96iYA_A0UT0hfgtsOb1hs-0zXbsqEodTHNn_aQJ-lHS4M-KAWb7-48_IwRFcBCurpEf3m601SFPVg",
"not-before-policy": 0,
"session_state": "b0c91c89-b820-4b2d-a55a-7ff80ce88e22",
"scope": "openid profile email"
}
次に、トークンをデコードします。
$ jwt decode eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJaNlRNcGRycnd3d25XYkZSZUFCRG1pdDUtam1jRURKcXg2YU9kdXBHLTZzIn0.eyJleHAiOjE2Njk2NDU0MTksImlhdCI6MTY2OTY0NTM1OSwianRpIjoiOWUyZjA2NGQtMTk1Yi00OTUzLWFiMDAtNGQ1MDJmOGU5ZmQ5IiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL2F1dGgvcmVhbG1zL21hc3RlciIsInN1YiI6IjI0YTgxOTJiLThjMzktNDgzZi05ZDc5LWRjMGYzYjg0ZjFlYyIsInR5cCI6IkJlYXJlciIsImF6cCI6ImFkbWluLWNsaSIsInNlc3Npb25fc3RhdGUiOiJiMGM5MWM4OS1iODIwLTRiMmQtYTU1YS03ZmY4MGNlODhlMjIiLCJhY3IiOiIxIiwic2NvcGUiOiJvcGVuaWQgcHJvZmlsZSBlbWFpbCIsInNpZCI6ImIwYzkxYzg5LWI4MjAtNGIyZC1hNTVhLTdmZjgwY2U4OGUyMiIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwicHJlZmVycmVkX3VzZXJuYW1lIjoiYWRtaW4iLCJncm91cCI6WyIvQWdyb3VwIiwiL0Jncm91cCJdfQ.WP1asd7M8uaUpr15k3-wxjWW6YTqna14B0KaB2E68ERHWmtfSOZiMaqn9mbYCS0aKTWJCBgkF189sJWTG5QVEU58j5xhzIZHr-0sR5BtkQdlJqgDTebTaMuKBHNDNVGfn_CQMNZ3M-kZQFFGhroqhzfRfmR5gTwYGxl1JgsWL0oX9MTDuZfW0RRK8S02TVwG4GSGrEBaYeOU4q_NtAoAPTJlMbkZSw5S9vNYh2ddkN8U27VB-c5QWljDZ3pczLG3JLHCCnXAfb9AJSr8mesu49IFMmSlQykvLoWoCfrb6U0k-F5me-U2yaTRtzBgLMidcOqJ4gkC4CiiAPlDsqTVxw
Token header
------------
{
"typ": "JWT",
"alg": "RS256",
"kid": "Z6TMpdrrwwwnWbFReABDmit5-jmcEDJqx6aOdupG-6s"
}
Token claims
------------
{
"acr": "1",
"azp": "admin-cli",
"email_verified": false,
"exp": 1669645419,
"group": [
"/Agroup",
"/Bgroup"
],
"iat": 1669645359,
"iss": "http://localhost:8080/auth/realms/master",
"jti": "9e2f064d-195b-4953-ab00-4d502f8e9fd9",
"preferred_username": "admin",
"scope": "openid profile email",
"session_state": "b0c91c89-b820-4b2d-a55a-7ff80ce88e22",
"sid": "b0c91c89-b820-4b2d-a55a-7ff80ce88e22",
"sub": "24a8192b-8c39-483f-9d79-dc0f3b84f1ec",
"typ": "Bearer"
}
おっ〜、ちゃんとgroupクレームが追加されてますね。
クレームの追加は、色々できるので、RH-SSOの管理画面を2つブラウザ起動しておいて、片方はEvaluateの画面で確認しながら、片方は設定を色々変更しながら、どういうふうに追加されるかを確認しながらやるのが個人的なオススメです。