Skip to content

Keycloakのロールについて学ぶ

はじめに

Keycloakは、オープンソースのアイデンティティおよびアクセス管理ソリューションであり、OpenID Connect や SAML を利用した認証ができるツールです。Keycloakの機能の1つに、ロールベースのアクセス制御があります。ロールは、特定の権限やアクセスレベルを持つユーザーグループに割り当てられ、アプリケーションやサービスへのアクセスを制御します。
しかし、Keycloak の管理画面を見ると、ロールの設定方法が複雑で理解しにくいです。そこで、この記事では、Keycloakのロールについて理解を深めるために、ロールの概念や設定方法について学習した内容をアウトプットします。

成果物

https://github.com/kntks/blog-code/tree/main/2024/05/keycloak-role

環境

バージョン
MacVentura 13.2.1
Keycloak23.0.6
Docker26.0.0
Docker Composev2.24.5
Terraform1.8.2

環境構築

compose.yaml
services:
keycloak:
image: quay.io/keycloak/keycloak:23.0.6
ports:
- target: 8080
published: 8080
protocol: tcp
mode: host
environment:
- KEYCLOAK_ADMIN=admin
- KEYCLOAK_ADMIN_PASSWORD=admin
command: ["start-dev"]

起動する

Terminal window
docker compose up

http://localhost:8080/auth にアクセスして、Keycloakの管理画面にログインします。デフォルトのユーザー名とパスワードは、admin です。

Terraform を使用するために Keycloak側のセットアップ が必要です。

terraform/terraform.tfvars
client_id = "terraform"
client_secret = "client secretをコピペ"
url = "http://localhost:8080"

terraform apply で Keycloak の設定を行います。

Terminal window
cd terraform
terraform init
terraform plan
terraform apply -auto-approve

ロールについて

Keycloakには、以下の2つのロールがあります。

  • Realm role(以下:レルムロール)
    レルムにつけるロールではなく、レルムで管理するロールです。レルム内のすべてのクライアントに対して使用できます。レルム内のすべてのアプリケーションに適用されるグローバル権限に使用されます。たとえば、管理者、ユーザー、ゲストなどのロールがあります。

  • Client role(以下:クライアントロール)
    クライアントにつけるロールではなく、クライアント内で管理する専用のロールです。各クライアントは、そのアプリケーション固有のアクセス許可を定義し独自のロールのセットを持つことができます

また、ロールを割り当てる対象は以下の3つがあります。

  • Group(以下:グループ)
    グループにつけるロールです。グループは、ユーザーの集合であり、ユーザーにロールを割り当てるための便利な方法です。グループにロールを割り当てることで、グループ内のすべてのユーザーにそのロールが自動的に割り当てられます。

  • User(以下:ユーザー)
    ユーザーとロールを定義します。マッピングを作成することでユーザーにレルムロールまたはクライアントロールを割り当てることでき、そのユーザーに特定のアクセス許可を付与できます。

  • Service account(以下:サービスアカウント)
    各クライアントには、アクセストークンを取得するためのサービスアカウントが組み込まれています。Client Credentials Grants を利用してアクセストークンを取得する際に使用します。

参考:

デフォルトで設定されているロール

Terraform でレルム(myrealm)、クライアント(myapp)、ユーザー(myuser)を作成しました。それぞれについているロールを確認してみましょう。

このセクションでは、アクセストークンと Keycloak の管理画面からロールを確認します。

管理画面からロールを確認する

レルムロール

レルムにデフォルトで設定されているロールを確認します。Realm settings -> User registration -> Default roles に表示されているロールが、レルムにデフォルトで設定されているロールです。 realm-roles-menu

クライアントロール

クライアントにデフォルトで設定されているロールを確認します。myapp のクライアントの詳細画面に移動し、Roles タブをクリックします。デフォルトでは、何も作成されていません。

default-client-roles

グループ

デフォルトでグループは作成されていないので、ロールは割り当てられていません。 default-group-roles

ユーザー

myuser をクリックして、ユーザーの詳細画面に移動します。 users-list

Role Mappings タブを開くと、ロールを確認できます。ユーザーに直接割り当てられているロール(default-role-myrealm)が表示されます。 default-user-role-mapping-hide-inherited-roles

Hide inherited roles のチェックを外すと、他に割り当てられている5つのロールが表示されます。
manage-account、manage-account-links、view-profile のロール名に account が付いていることから、account クライアントのロールであることがわかります。 default-user-role-mapping

default-role-myrealm 以外のロールは、Realm settings -> User registration -> Default roles にある設定によって自動的に割り当てられています。 default-realm-roles

Use default roles to automatically assign user role mappings when a user is created

(訳)デフォルトのロールを使用して、ユーザー作成時にユーザーのロールマッピングを自動的に割り当てます。

引用:Using default roles - Keycloak Documentation

サービスアカウント

クライアント詳細画面から Service accounts roles タブをクリックし、Hide inherited roles のチェックを外すと、サービスアカウントに割り当てられているロールが表示されます。 default-client-service-account-roles

アクセストークンのロールを確認する

先ほどは管理画面からロールの確認をしました。今度は、アクセストークンのロールを確認します。アクセストークンは、Resource Owner Password Credentials Grants (Direct Access Grants)とClient Credentials Grants を使用して取得します。それぞれの方法で access_token を取得します。

ユーザー

Resource Owner Password Credentials Grant でアクセストークンを取得する例

Terminal window
curl -s \
-d "client_id=myapp" \
-d "client_secret=xxxxxxx" \
-d "username=myuser" \
-d "password=myuser" \
-d "grant_type=password" \
-d "scope=openid" \
"http://localhost:8080/realms/myrealm/protocol/openid-connect/token" | jq

その後、取得した access_token を jwt.io に貼り付け、Payload を確認します。
※ロールに関する情報のみ表示しています。

{
"aud": "account",
"realm_access": {
"roles": [
"default-roles-myrealm",
"offline_access",
"uma_authorization"
]
},
"resource_access": {
"account": {
"roles": [
"manage-account",
"manage-account-links",
"view-profile"
]
}
},
"scope": "openid profile email",
}

サービスアカウント

Client Credentials Grants でアクセストークンを取得する例

Terminal window
curl -s -X POST \
--header 'Content-Type: application/x-www-form-urlencoded' \
-d 'grant_type=client_credentials' \
-d 'client_id=myapp' \
-d 'client_secret=xxxxxxx' \
"http://localhost:8080/realms/myrealm/protocol/openid-connect/token" | jq

その後、取得した access_token を jwt.io に貼り付け、Payload を確認します。
※ロールに関する情報のみ表示しています。

{
"aud": "account",
"realm_access": {
"roles": [
"default-roles-myrealm",
"offline_access",
"uma_authorization"
]
},
"resource_access": {
"account": {
"roles": [
"manage-account",
"manage-account-links",
"view-profile"
]
}
},
"scope": "profile email",
}

クライアントスコープとロールスコープマッピング

クライアントスコープとは、Keycloak においてレルムレベルで設定され、クライアントにリンクできるエンティティです。

Keycloak には、Client scopes という名前がついているものが2つあり、画像左のナビゲーションペインにある Client scopes と Clients の詳細画面の Client scopes タブです。

紛らわしいのでこの記事では、左のナビゲーションペインにある Client scopes を、“クライアントスコープメニュー”、Clients 詳細画面の Client scopes タブは”クライアントスコープタブ”と呼ぶことにします。 client-scopes

クライアントスコープメニューから作成できるクライアントスコープは、レルム内にあるすべてのクライアントと共有でき、クライアントスコープタブから個別のクライアントにクライアントスコープをリンクできます。クライアントスコープは、クライアントにリンクされると、そのクライアントに対してアクセストークンのスコープとして使用できるようになります。

アクセストークンの”scope”を追加する

ここではクライアントスコープを作成し、クライアントにリンクすることで、アクセストークンの “scope” に追加してみます。

クライアントスコープを作成する

クライアントスコープでは、クライアント共有のクライアントスコープを作成できます。 client-scopes-create-client-scope

my-client-scope というクライアントスコープを作成します。 client-scopes-create-client-scope-2

クライアントとクライアントスコープを関連付ける

次にクライアントスコープタブに移動し、my-client-scopemyapp クライアントにリンクします。 client-scopes-tab-add-client-scope

今回は Optional で追加します。 client-scopes-tab-add-client-scope-optional

アクセストークンの”scope”を確認する

以下のコードを実行し、アクセストークンを取得します。

Terminal window
curl -s \
-d "client_id=myapp" \
-d "client_secret=xxxxxxx" \
-d "username=myuser" \
-d "password=myuser" \
-d "grant_type=password" \
-d "scope=openid" \
"http://localhost:8080/realms/myrealm/protocol/openid-connect/token" | jq

その後、取得した access_token を jwt.io に貼り付け、Payload から “scope” を確認します。

{
"scope": "openid profile email",
}

次は scope に my-client-scope を追加してアクセストークンを取得します。

Terminal window
curl -s \
-d "client_id=myapp" \
-d "client_secret=xxxxxxx" \
-d "username=myuser" \
-d "password=myuser" \
-d "grant_type=password" \
-d "scope=openid" \
-d "scope=openid my-client-scope" \
"http://localhost:8080/realms/myrealm/protocol/openid-connect/token" | jq

こちらも同様に取得した access_token を jwt.io に貼り付け、Payload から “scope” を確認します。
先ほど作成した my-client-scope が追加されていることが確認できます。

{
"scope": "openid profile my-client-scope email"
}

クライアントスコープをクライアントに関連付ける際に、Assigned typeOptional にしました。これを Default に変更した場合は、トークンエンドポイントへのリクエストに scope パラメータが “openid” のみでも、my-client-scope が自動的に追加されます。

アクセストークンに付与されるロール

このセクションでは、ロールスコープマッピングについて学習します。

ロールスコープマッピングは以下の引用からアクセストークンに付与されるロールに関係していることがわかります。

Role Scope Mappings limit the roles declared inside an access token. When a client requests a user authentication, the access token they receive contains only the role mappings that are explicitly specified for the client’s scope.

(訳)ロールスコープマッピングは、アクセストークン内で宣言されるロールを制限します。クライアントがユーザ認証を要求したとき、彼らが受け取るアクセストークンには、クライアントのスコープに対して明示的に指定されたロールマッピングのみが含まれます。

引用:Role scope mappings

続いての引用から、デフォルトではユーザーに割り当てられているすべてのロールがアクセストークンに含まれることがわかります。

By default, each client gets all the role mappings of the user.

(訳)デフォルトでは、各クライアントはユーザーのすべてのロールマッピングを取得します。

引用:Role scope mappings

アクセストークンからデフォルトのロールを削除する

先ほどの”アクセストークンのロールを確認する”のセクションではアクセストークンのロールを確認しました。

しかし、デフォルトではユーザーに割り当てられているすべてのロールがアクセストークンに含まれるため、アクセストークンのサイズが大きくなる可能性があります。実際、以下の Realm settings -> User registration -> Default roles にロールを追加すると自動的にアクセストークンに含まれます。 default-realm-roles

デフォルトのロールを削除しても良いのですが、クライアントスコープタブから Full Scope Allowed を Off にすることで、アクセストークンについていたデフォルトのロールを削除できます。

Clients -> myapp -> Client scopes -> myapp-dedicated -> Scope の順にページ移動します。 clients-myapp-client-scopes-tab

Full scope allowed をクリックして “Off” にします。 clients-myapp-client-scopes-tab-scope-full-scope-allowd

再度アクセストークンを取得します。

Terminal window
curl -s \
-d "client_id=myapp" \
-d "client_secret=xxxxxxx" \
-d "username=myuser" \
-d "password=myuser" \
-d "grant_type=password" \
-d "scope=openid" \
"http://localhost:8080/realms/myrealm/protocol/openid-connect/token" | jq

Payload を確認すると、audresource_accessrealm_access が削除されていることが確認できます。

{
"aud": "account",
"realm_access": {
"roles": [
"default-roles-myrealm",
"offline_access",
"uma_authorization"
]
},
"resource_access": {
"account": {
"roles": [
"manage-account",
"manage-account-links",
"view-profile"
]
}
},
"scope": "openid profile email",
}

myapp ユーザーの Role mapping を確認すると、デフォルトのロールがついていますが、アクセストークンには含まれていません。 default-user-role-mapping

カスタムロールをアクセストークンに追加する

先ほど Off にした Full Scope Allowed はそのままにしておきます。

ここでは、レルムロール、myapp のクライアントロール、account のクライアントロールをユーザーに割り当て、アクセストークンに含まれるロールを確認します。

myapp クライアントの詳細画面から Role タブに移動し my-client-role ロール(クライアントロール)を作成します。 clients-myapp-roles-create-role

その後 Users -> myuser -> Role mapping に移動し、Assign role ボタンを押します。 users-myuser-role-mapping-tab

myuser のロールに my-client-role と、manage-account を追加します。myapp クライアントのロールと account クライアントのロールを割り当てることで、アクセストークンに含まれるロールを確認してみます。 users-myuser-role-mapping-assign-roles

現在ユーザーに割り当てられているロールは以下の通りです。 users-myuser-role-mapping-assigned

いつものようにアクセストークンを発行してみます。

Terminal window
curl -s \
-d "client_id=myapp" \
-d "client_secret=xxxxxxx" \
-d "username=myuser" \
-d "password=myuser" \
-d "grant_type=password" \
-d "scope=openid" \
"http://localhost:8080/realms/myrealm/protocol/openid-connect/token" | jq

access_token の Payload を確認してみると、resource_access に myapp のクライアントロールである my-client-role のみ表示され、アクセストークンには、aud が存在しませんでした。

{
"resource_access": {
"myapp": {
"roles": [
"my-client-role"
]
}
},
"scope": "openid profile email"
}

Full Scope Allowed が Off の場合、(デフォルトで作成されている)roles クライアントスコープは、ユーザーのロールをアクセストークンに追加するために使用されます。(実際に Assign type を Default から Optional に変更すると my-client-role もアクセストークンから無くなります。) client-scopes-list

roles クライアントスコープの Mappers client-scopes-roles-mappers

以下は、roles クライアントスコープの説明です。

This scope has mappers, which are used to add the roles of the user to the access token and add audiences for clients that have at least one client role.

(訳)このスコープにはマッパーがあり、アクセストークンにユーザーのロールを追加し、少なくとも1つのクライアントロールを持つクライアントのオーディエンスを追加するために使用されます。

引用:Protocol - Keycloak Documentation

roles クライアントスコープのスコープには何も設定していませんが、どうやら、myuser が持っているロールの中から、client_id が myapp であるクライアントにリンクされているクライアントロールのみがアクセストークンに含まれるようです。(メモ:この挙動はドキュメントを探しても見つかりませんでした。)

参考:

まとめ

この記事では、Keycloak のロールについて学習しました。Keycloak には、レルムロール、クライアントロール、グループロールの3つのロールがあります。また、ロールを割り当てる対象は、ユーザー、グループ、サービスアカウントの3つがあります。ロールを割り当てる対象にロールを割り当てることで、アプリケーションやサービスへのアクセスを制御できます。さらにクライアントスコープを使用することで、アクセストークンのスコープを制御できます。

さいごに

Keyclokaのクライアントスコープやマッパー周りは複雑で難しいです。さらに Usability improvements for Client Scope configuration #19869 のディスカッションにもあるように将来的にUIが改善されたり、Dedicated scopes が廃止されるかもしれません。そのため、もし Keycloak 24 以上を使用している場合は最新のドキュメントを参照することをオススメします。

参考