Skip to content
Published on

Keycloak Authorization Services — UMA 2.0 による細粒度のアクセス制御

Authors

はじめに

「このユーザーは admin role を持っているか?」という問いだけで権限を決定できるシステムは、思ったより早く限界にぶつかります。「このユーザーはこのドキュメント編集できるか? ただし、ドキュメントの所有者であるか、同じ部署のマネージャーであるか、業務時間内のリクエストである場合に限る」という要件が来た瞬間、role ベースの分岐はアプリケーションコード全体に散らばり始めます。

2026 年現在、この問題意識はさらに切実になっています。AI エージェントがユーザーの代わりに API を呼び出す時代(Keycloak 26.6 は MCP authorization server の役割のための OAuth Client ID Metadata Document を実験的にサポートしています)には、「誰が」だけでなく「何が、どの委任スコープで、どのリソースに」アクセスするかを中央で評価・監査できなければなりません。本記事では Keycloak Authorization Services のモデルと UMA 2.0 フローを解剖し、実践的な policy enforcer の設定、パフォーマンス考慮事項、そして OPA/OpenFGA のような外部認可エンジンとの関係まで扱います。

RBAC の限界、そして ABAC/ReBAC

まず認可モデルのスペクトラムを整理しましょう。

モデル決定基準限界
RBACユーザーに付与された roleadmin は全ドキュメント編集可能リソース単位の区別不可、role 爆発
ABAC主体・リソース・環境の属性同じ部署 + 業務時間なら許可ポリシーの作成・デバッグが困難
ReBAC主体とリソース間の関係グラフドキュメントの owner かフォルダの editor なら許可関係データの同期コスト

RBAC の典型的な失敗パターンは **role 爆発(role explosion)**です。「プロジェクト A の編集者」「プロジェクト B の閲覧者」をすべて role にすると、プロジェクト数 × 権限数だけ role が増え、結局 role 管理自体が新たな権限問題になります。かといってアプリケーションコードの if 分岐で解決すると、ポリシーがコードのあちこちに散らばり、監査も変更も困難になります。

ここで必要なのが**ポリシーの中央化と外部化(policy externalization)**です。Keycloak Authorization Services は OAuth 2.0 の上でリソース単位の細粒度認可を中央で評価するフレームワークであり、RBAC を含め ABAC、時間ベース、そして限定的な ReBAC スタイルまで表現できます。

Keycloak Authorization Services のアーキテクチャ

4 つのビルディングブロック

Keycloak の認可モデルは 4 つの概念で構成されます。client 設定で Authorization Enabled を有効にすると、その client が「resource server」となり、その下に以下を定義します。

+------------------------------------------------------------+
|                Resource Server (client)                    |
|                                                            |
|  +-----------+     +---------+                             |
|  | Resource  |---->| Scope   |   「何を」 (document:123)     |
|  | (文書,API) |     | (view,  |   「どの行為」 (view, edit)   |
|  +-----------+     |  edit)  |                             |
|        ^           +---------+                             |
|        |                ^                                  |
|  +-----+----------------+-----+                            |
|  |       Permission           |  「リソース/スコープと        |
|  | (resource-based /          |    ポリシーを結合」           |
|  |  scope-based)              |                            |
|  +-------------+--------------+                            |
|                |                                           |
|  +-------------v--------------+                            |
|  |          Policy            |  「誰が/どの条件で」 (role,   |
|  | (role, user, group, time,  |   group, time, regex ...)  |
|  |  regex, aggregated ...)    |                            |
|  +----------------------------+                            |
+------------------------------------------------------------+
  • Resource:保護対象です。URI パターン、タイプ、所有者(owner)を持つことができます。「ドキュメント 123」のような個別インスタンスにも、「document タイプ全体」にもなり得ます。
  • Scope:リソースに対して実行可能な行為です。view、edit、delete のような動詞を定義します。
  • Policy:「誰が/どの条件で」を定義する単位です。ポリシー自体はリソースを知りません。
  • Permission:リソース(またはスコープ)とポリシーを結合し、「このリソースのこの行為はこれらのポリシーを通過しなければならない」と宣言します。

この分離が重要なのはポリシーの再利用性のためです。「業務時間内のみ」という time policy を一度作れば、数十の permission で再利用できます。

Policy 種類の整理

Policy 種類評価基準活用例
Rolerealm/client role の保有管理者専用機能
User特定ユーザーの指定システムアカウントの例外
Groupグループメンバーシップ(階層対応)部署単位のアクセス
Clientリクエストした client の識別内部サービス専用 API
Time時刻・曜日・期間の条件業務時間制限、キャンペーン期間
Regexトークンクレームへの正規表現マッチメールドメイン、属性パターン
Client Scopeclient scope の保有同意ベースのアクセス
Aggregated複数ポリシーの組み合わせ複合条件
JavaScriptJS コードによる任意ロジック(deprecated の流れ)レガシーカスタムロジック

JavaScript policy への注意が必要です。かつては Admin Console で JS コードを直接入力してポリシーを作成できましたが、セキュリティ上の理由でこの機能はデフォルト無効となり、デプロイ JAR を通じてのみ限定的に使用できる deprecated な経路となりました。Nashorn エンジン削除以降の流れまで考慮すると、新規設計で JS policy に依存するのは避けるべきです。複雑なカスタムロジックが必要なら、(1) 既存のポリシー種類の組み合わせ(aggregated policy)で解決する、(2) Policy SPI で Java ベースのカスタムポリシーを実装する、(3) 後述する外部認可エンジンとの併用を検討する、のいずれかを選びましょう。

PEP/PDP モデル

Authorization Services は古典的な XACML 用語の構造に従います。

   +----------------+    (1) リクエスト    +---------------------+
   |    Client      +-------------------->+   Application       |
   | (ブラウザ/アプリ)|                     |   = PEP             |
   +----------------+                     | (Policy Enforcement |
                                          |       Point)        |
                                          +----------+----------+
                                                     | (2) 決定リクエスト
                                                     |  (token / ticket)
                                          +----------v----------+
                                          |     Keycloak        |
                                          |   = PDP + PAP       |
                                          | (Policy Decision /  |
                                          |  Administration)    |
                                          +----------+----------+
                                                     | (3) Permit/Deny
                                                     |  (RPT 発行)
                                          +----------v----------+
                                          |  保護されたリソース    |
                                          +---------------------+
  • PEP(Policy Enforcement Point):アプリケーション側でリクエストをインターセプトし、認可決定を強制するポイント。Keycloak が提供する policy enforcer ライブラリがこの役割を担います。
  • PDP(Policy Decision Point):ポリシーを評価して Permit/Deny を決定するポイント。Keycloak サーバーの token エンドポイント(UMA grant)が担当します。
  • PAP(Policy Administration Point):ポリシーを管理するポイント。Admin Console と Protection API です。

UMA 2.0 Grant フローの解剖

UMA(User-Managed Access)2.0 は OAuth 2.0 の拡張で、「リソース所有者が自分のリソースへのアクセスポリシーを管理し、クライアントは permission ticket を通じて認可を交渉する」というモデルです。Keycloak でのフローを段階的に見ていきます。

 Client                Resource Server (PEP)            Keycloak (PDP)
   |                          |                              |
   | (1) GET /api/doc/123     |                              |
   |  (access token, RPTなし) |                              |
   +------------------------->|                              |
   |                          | (2) permission ticket 要求   |
   |                          +----------------------------->|
   |                          |    POST /authz/protection/   |
   |                          |         permission           |
   |                          |<-----------------------------+
   | (3) 401 + WWW-Authenticate: UMA                         |
   |     (as_uri, ticket)     |                              |
   |<-------------------------+                              |
   |                          |                              |
   | (4) POST /token                                         |
   |     grant_type=uma-ticket, ticket=...                   |
   +-------------------------------------------------------->|
   |                          |       (5) ポリシー評価        |
   | (6) RPT (権限を内蔵したトークン)                           |
   |<--------------------------------------------------------+
   |                          |                              |
   | (7) GET /api/doc/123 (RPT)                              |
   +------------------------->| (8) RPT 検証後レスポンス      |
   |<-------------------------+                              |

Permission Ticket と RPT

二つのトークン概念が登場します。

  • Permission ticket:resource server が「このリクエストには document:123 への view 権限が必要だ」という事実を Keycloak に登録して受け取る一回限りのチケットです。クライアントはこのチケットを持って token エンドポイントへ行き、認可を要求します。
  • RPT(Requesting Party Token):ポリシー評価を通過したクライアントに発行される、permission クレームを内蔵した access token です。どのリソースのどのスコープが許可されたかがトークンの中に入っています。

実際の HTTP リクエストで見ると次の通りです。

POST /realms/myrealm/protocol/openid-connect/token HTTP/1.1
Host: sso.example.com
Authorization: Bearer ACCESS_TOKEN
Content-Type: application/x-www-form-urlencoded

grant_type=urn:ietf:params:oauth:grant-type:uma-ticket
&ticket=PERMISSION_TICKET_VALUE
&submit_request=false

チケットなしで resource server の client_id を audience に指定し、「自分が持つすべての権限を評価してほしい」とリクエストすることもできます。これは UMA プロトコルの完全な往復を省略する実用的なショートカットとして頻繁に使われます。

POST /realms/myrealm/protocol/openid-connect/token HTTP/1.1
Host: sso.example.com
Authorization: Bearer ACCESS_TOKEN
Content-Type: application/x-www-form-urlencoded

grant_type=urn:ietf:params:oauth:grant-type:uma-ticket
&audience=document-service
&permission=document-resource#view
&response_mode=decision

response_mode=decision でリクエストすると、RPT の代わりにシンプルな許可可否の JSON を受け取ります。RPT が不要な単発の決定評価に便利です。

{
  "result": true
}

発行された RPT の permission クレームは次のような構造です。

{
  "authorization": {
    "permissions": [
      {
        "rsid": "8f4d2e1a-resource-uuid",
        "rsname": "document-123",
        "scopes": ["view", "comment"]
      }
    ]
  },
  "aud": "document-service",
  "exp": 1781234567
}

Protection API とリソースの動的登録

UMA のもう一つの柱は Protection API です。resource server は service account トークン(PAT、Protection API Token)でリソースを動的に登録・管理できます。「ユーザーがドキュメントを作成したら、そのドキュメントを owner とともにリソースとして登録する」パターンが代表的です。

# ドキュメント作成時のリソース動的登録
curl -X POST "https://sso.example.com/realms/myrealm/authz/protection/resource_set" \
  -H "Authorization: Bearer PAT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "document-123",
    "type": "urn:document-service:resources:document",
    "owner": "jdoe",
    "ownerManagedAccess": true,
    "resource_scopes": ["view", "edit", "delete", "share"],
    "uris": ["/api/documents/123"]
  }'

ownerManagedAccess を有効にすると、リソース所有者が Account Console から直接、他のユーザーにアクセスを共有したり、アクセスリクエストを承認・拒否したりできます。「自分のドキュメントを同僚に共有する」といったユーザー主導の共有シナリオこそ、UMA 本来の設計目的です。

Policy Enforcer 実践設定(Java)

理論をコードに落とし込みましょう。Keycloak が提供する keycloak-policy-enforcer ライブラリは、Java アプリケーションに PEP を組み込む標準的な方法です(旧 Keycloak adapter は deprecated となり、Spring Security + policy enforcer の組み合わせが現在の推奨経路です)。

<!-- pom.xml -->
<dependency>
  <groupId>org.keycloak</groupId>
  <artifactId>keycloak-policy-enforcer</artifactId>
  <version>26.0.0</version>
</dependency>

enforcer の設定ファイルです。

{
  "realm": "myrealm",
  "auth-server-url": "https://sso.example.com",
  "resource": "document-service",
  "credentials": {
    "secret": "CLIENT_SECRET_FROM_VAULT"
  },
  "http-method-as-scope": true,
  "lazy-load-paths": true,
  "enforcement-mode": "ENFORCING",
  "paths": [
    {
      "path": "/api/documents/*",
      "claim-information-point": {
        "claims": {
          "request.ip": "{request.remoteAddr}"
        }
      }
    },
    {
      "path": "/api/health",
      "enforcement-mode": "DISABLED"
    }
  ]
}

Spring Security のフィルターチェーンに enforcer を接続します。

@Configuration
@EnableWebSecurity
public class SecurityConfig {

  @Bean
  public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
      .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()))
      .authorizeHttpRequests(authz -> authz.anyRequest().authenticated())
      .addFilterAfter(createPolicyEnforcerFilter(), BearerTokenAuthenticationFilter.class);
    return http.build();
  }

  private ServletPolicyEnforcerFilter createPolicyEnforcerFilter() {
    return new ServletPolicyEnforcerFilter(request -> {
      try (InputStream is = getClass().getResourceAsStream("/policy-enforcer.json")) {
        return JsonSerialization.readValue(is, PolicyEnforcerConfig.class);
      } catch (IOException e) {
        throw new UncheckedIOException(e);
      }
    });
  }
}

設定ポイントを押さえておきます。

  • http-method-as-scope:HTTP メソッド(GET、POST、DELETE)をスコープにマッピングします。REST API ならリソース定義がシンプルになります。
  • lazy-load-paths:起動時に全リソースを先読みせず、リクエスト時にパスごとのリソースを照会します。リソースが数千以上なら必須です。
  • enforcement-mode:ENFORCING(マッピングのないパスを拒否)、PERMISSIVE(マッピングのないパスを許可)、DISABLED をパス単位でも指定できます。導入初期は PERMISSIVE で始めてカバレッジを広げる漸進戦略が安全です。
  • claim-information-point(CIP):リクエストコンテキスト(IP、ヘッダー、ボディ)をクレームとして抽出し、ポリシー評価に渡します。ABAC スタイルのポリシーの入力として活用されます。

Decision Strategy — ポリシー衝突の解釈

一つの permission に複数のポリシーが付くと、結果をどう合算するかが decision strategy です。

戦略意味使用例
Unanimous(デフォルト)すべてのポリシーが Permit なら Permitセキュリティ優先、デフォルト推奨
Affirmative一つでも Permit なら Permit「管理者または所有者」
ConsensusPermit 数が Deny 数より多ければ Permitまれ、投票型シナリオ

よくある罠は Affirmative が必要な場所に Unanimous を置くことです。「所有者ポリシー + 管理者 role ポリシー」を Unanimous で束ねると、「所有者かつ同時に管理者」だけが通過します。OR の意味なら permission の decision strategy を Affirmative に変えるか、aggregated policy の中で戦略を指定して再構成する必要があります。

もう一点、resource server 全体レベルの decision strategy(複数の permission が同じリソースにかかる場合の合算)も別途存在します。permission レベルと resource server レベルの戦略を混同するとデバッグが困難になるため、Admin Console の Evaluate タブでシミュレーションしながら確認する習慣をつけましょう。Evaluate タブは、特定のユーザー・リソース・スコープの組み合わせに対してどのポリシーがどんな結果を出したかを段階的に表示する、この機能群で最も有用なデバッグツールです。

パフォーマンス考慮事項

細粒度の認可はタダではありません。設計時に以下を検討すべきです。

  • 評価往復コスト:ENFORCING モードの PEP はキャッシュミス時に Keycloak へ評価リクエストを送ります。リクエストあたり数〜数十 ms が追加され得るため、enforcer のパス・決定キャッシュ設定を調整し、RPT の再利用(クライアントに RPT を持たせる)を活用します。
  • リソース数の爆発:ドキュメントごとにリソースを登録するインスタンス単位モデルは、数百万リソースにつながり得ます。Keycloak のリソース・ポリシーは RDB に保存されるため、この規模ではタイプ単位リソース + CIP でインスタンス判断(所有者クレームの比較)を行うハイブリッドモデルや、外部 ReBAC エンジンを検討すべきです。
  • トークンサイズ:RPT に permission が数百個入るとトークンが数十 KB に肥大化し、ヘッダー制限に引っかかります。permission パラメータで必要なリソースだけを要求するか、decision モードを使いましょう。
  • DB 負荷:ポリシー評価は Keycloak DB の照会を伴います。認可トラフィックはログイントラフィックよりはるかに多いという点を、キャパシティプランニングに反映する必要があります。
経験的ガイドライン
------------------------------------------------------
リソース数 < 1 万、評価 QPS < 数百   → Keycloak authz 単独で十分
リソース数 10 万+、関係ベースの判断   → 外部 ReBAC (OpenFGA) の併用を検討
ポリシーがコード/データ中心 (デプロイパイプラインの規則など) → OPA の併用を検討

外部認可エンジンとの比較 — OPA、OpenFGA

2026 年の認可エコシステムにおいて、Keycloak Authorization Services は唯一の選択肢ではありません。代表的な二つの外部エンジンと比較してみましょう。

項目Keycloak AuthzOPA (Rego)OpenFGA (Zanzibar 系)
モデルresource/scope/policy汎用ポリシーエンジン(ABAC に強い)関係タプルグラフ(ReBAC)
ポリシー言語Admin UI + ポリシー種類Rego(宣言的言語)DSL(関係モデル定義)
データの所在Keycloak DB入力として注入(サイドカー/バンドル)独自のタプルストア
強みIdP と統合、UMA、ユーザー主導の共有インフラ全般の汎用ポリシー大規模な関係クエリ (listObjects)
弱み超大規模リソース、関係グラフデータ同期は別途の課題IdP 機能なし、別途運用
標準UMA 2.0、OAuth事実上の標準 (CNCF)Zanzibar 論文ベース、OpenID AuthZEN の議論に参加

重要な洞察は、これらは競合関係というよりレイヤーが異なるという点です。Keycloak は「誰が認証され、どんなトークンを持っているか」の真実の源泉であり、OPA/OpenFGA はそのトークンを入力の一つとして受け取り、より複雑な決定を下す PDP になり得ます。

併用パターン

実務で実証された組み合わせパターンは次の通りです。

パターン A: Keycloak(認証+coarse) + OPA(fine-grained、インフラポリシー)
+--------+   JWT   +-------------+  input(JWT claims,  +-----+
| Client +-------->+ API Gateway +-------------------->+ OPA |
+--------+         |  / Service  |   resource attrs)   +-----+
                   +-------------+<--------------------+
                                     allow / deny

パターン B: Keycloak(認証) + OpenFGA(関係ベース認可)
+--------+   JWT   +----------+  check(user, relation, +---------+
| Client +-------->+ Service  +----------------------->+ OpenFGA |
+--------+         +----------+        object)         +---------+
                        |                                   ^
                        |  書き込み時に関係タプルを同期        |
                        +-----------------------------------+
  • パターン A(Keycloak + OPA):Keycloak が発行した JWT のクレーム(role、group、部署属性)を OPA ポリシーの入力として使います。ゲートウェイ・サービスメッシュレベルのポリシー(この経路は内部ネットワークのみ、この API は特定 scope のみ)に OPA が強く、ユーザーの識別属性は Keycloak が責任を持ちます。Keycloak の protocol mapper でポリシー評価に必要な属性をトークンに載せて送るのが連結点です。
  • パターン B(Keycloak + OpenFGA):Google Zanzibar 論文の系譜を継ぐ OpenFGA は、「ドキュメント → フォルダ → チーム」のように関係が伝播するモデルと、「このユーザーが閲覧できるドキュメントの一覧」(listObjects)クエリに強みがあります。Keycloak は認証とユーザー識別子を提供し、ドメインサービスがリソースの作成・共有時に OpenFGA へ関係タプルを書き込みます。
  • ハイブリッド:coarse-grained(この API を呼び出せるか)は Keycloak の scope/role としてトークンに載せてゲートウェイで素早くフィルタし、fine-grained(このオブジェクトにアクセスできるか)は OpenFGA/OPA でサービス内部で評価する 2 段構造が、大規模システムの一般解として定着しました。

Keycloak Authorization Services が最も輝く領域は、UMA ベースのユーザー主導リソース共有(Account Console 統合)と、IdP とポリシー管理の一元化がもたらす運用のシンプルさです。逆に、数百万オブジェクトの関係クエリが核心であれば、最初から ReBAC エンジンを別レイヤーとして置く方が良いでしょう。

運用ベストプラクティス

  • PERMISSIVE から ENFORCING へ:既存サービスに導入する際は enforcement mode を PERMISSIVE にして拒否予定ログだけを収集し、ポリシーカバレッジを検証してから切り替えます。
  • Evaluate タブを CI のように:ポリシー変更時に代表シナリオ(所有者、他部署、未ログイン、時間外)を Evaluate API で自動検証する回帰テストを設ければ、ポリシー事故を大幅に減らせます。
  • ポリシー命名規則:policy-対象-条件 のような一貫した命名(例:policy-document-owner-only)がないと、数十のポリシーの中で迷子になります。
  • export で構成管理:client の authorization 設定は JSON で export/import できます。ポリシーを Git にコミットし、環境間で昇格(promote)するパイプラインを構成しましょう。
  • JS policy 依存の排除:アップグレード経路において JS policy は負債です。aggregated/regex/CIP の組み合わせ、または SPI 実装での代替計画を立てましょう。
  • audience 検証:RPT を受け取るサービスは、必ず aud クレームが自分自身であることを検証すべきです。他サービス向け RPT の再利用攻撃を防ぐ基本です。

トラブルシューティングノート

症状原因候補対応
すべてのリクエストが 403ENFORCING + パス未マッピングパス設定、PERMISSIVE で切り分けテスト
所有者なのに拒否されるdecision strategy が UnanimousAffirmative へ変更、または aggregated を再構成
RPT の permission が空audience 欠落、リソース未マッチaudience パラメータ、リソース URI パターンの確認
評価が遅いlazy-load 未使用、リソース過多lazy-load-paths、タイプ単位リソースへ再設計
トークンが大きすぎる全権限を RPT に内包permission パラメータで範囲縮小、decision モード
ポリシー変更が反映されないenforcer のキャッシュキャッシュ TTL 確認、再起動・無効化

おわりに

Keycloak Authorization Services は、「権限ロジックをコードから取り出し、中央で宣言的に管理する」という目標を OAuth/UMA 標準の上で実現した、思った以上に奥深いフレームワークです。resource/scope/policy/permission の 4 分割モデルと decision strategy を理解すれば、RBAC では表現不可能だった要件をエレガントに解決でき、UMA 2.0 の permission ticket フローはユーザー主導の共有という独自の価値を提供します。

同時に限界も明確です。数百万リソースの関係クエリは Zanzibar 系エンジンの領域であり、インフラ全般の汎用ポリシーは OPA の領域です。2026 年の現実的な正解は「Keycloak で認証と coarse-grained を、必要な場所に ReBAC・ポリシーエンジンをレイヤーとして載せる」構成であり、そのすべてのレイヤーの入力となる信頼できるトークンを作ることが、Keycloak の変わらぬ役割です。次回は Keycloak の可観測性 — メトリクス、監査ログ、イベントベースのモニタリングを扱います。

参考資料