Keycloak policy enforcer

JavaアプリケーションでのKeycloak policy enforcerの使用

ポリシー施行ポイント(PEP)は設計パターンであり、さまざまな方法で実装できます。Keycloakは、さまざまなプラットフォーム、環境、プログラミング言語向けのPEPを実装するために必要なすべての手段を提供します。Keycloak Authorization Servicesは、RESTful APIを提供し、集中型認可サーバーを使用したきめ細かい認可のためにOAuth2認可機能を活用します。

PEP overview

PEPは、保護されたリソースに関連付けられたポリシーを評価することによって決定が下されるKeycloakサーバーからのアクセス決定を施行する責任があります。これは、特定の保護されたリソースへのリクエストが、これらの決定によって付与された権限に基づいて満たされるかどうかを確認するために、アプリケーションのフィルターまたはインターセプターとして機能します。

Keycloakは、JakartaEE準拠のフレームワークとWebコンテナをセキュアにするための組み込みサポートを備えたJavaアプリケーションでKeycloak Policy Enforcerを有効にするための組み込みサポートを提供します。Mavenを使用している場合は、プロジェクトに次の依存関係を設定する必要があります

<dependency>
    <groupId>org.keycloak</groupId>
    <artifactId>keycloak-policy-enforcer</artifactId>
    <version>26.0.4</version>
</dependency>

ポリシーエンフォーサーを有効にすると、アプリケーションに送信されたすべてのリクエストがインターセプトされ、保護されたリソースへのアクセスは、リクエストを行うアイデンティティにKeycloakによって付与された権限に応じて許可されます。

ポリシー施行は、アプリケーションのパスと、Keycloak管理コンソールを使用してリソースサーバー用に作成したリソースに強く関連付けられています。デフォルトでは、リソースサーバーを作成すると、Keycloakはリソースサーバーのデフォルト構成を作成するため、ポリシー施行を迅速に有効にできます。

構成

ポリシーエンフォーサーの構成はJSON形式を使用しており、リソースサーバーから利用可能なリソースに基づいて保護されたパスを自動的に解決する場合は、ほとんどの場合、何も設定する必要はありません。

保護されているリソースを手動で定義する場合は、もう少し冗長な形式を使用できます

{
  "enforcement-mode" : "ENFORCING",
  "paths": [
    {
      "path" : "/users/*",
      "methods" : [
        {
          "method": "GET",
          "scopes" : ["urn:app.com:scopes:view"]
        },
        {
          "method": "POST",
          "scopes" : ["urn:app.com:scopes:create"]
        }
      ]
    }
  ]
}

以下は、各構成オプションの説明です

  • enforcement-mode

    ポリシーの施行方法を指定します。

    • ENFORCING(施行)

      (デフォルトモード)特定のリソースに関連付けられたポリシーがない場合でも、リクエストはデフォルトで拒否されます。

    • PERMISSIVE(許容)

      特定のリソースに関連付けられたポリシーがない場合でも、リクエストは許可されます。

    • DISABLED(無効)

      ポリシーの評価を完全に無効にし、任意のリソースへのアクセスを許可します。enforcement-modeDISABLEDの場合でも、アプリケーションは認可コンテキストを介してKeycloakによって付与されたすべての権限を取得できます。

  • on-deny-redirect-to

    サーバーから「アクセス拒否」メッセージが取得されたときに、クライアントリクエストがリダイレクトされるURLを定義します。デフォルトでは、アダプターは403 HTTPステータスコードで応答します。

  • path-cache

    ポリシーエンフォーサーが、アプリケーションのパスとKeycloakで定義されたリソース間の関連付けを追跡する方法を定義します。このキャッシュは、パスと保護されたリソース間の関連付けをキャッシュすることにより、Keycloakサーバーへの不要なリクエストを回避するために必要です。

    • lifespan

      エントリを期限切れにするまでの時間(ミリ秒単位)を定義します。指定しない場合、デフォルト値は30000です。0に等しい値は、キャッシュを完全に無効にするために設定できます。-1に等しい値は、キャッシュの有効期限を無効にするために設定できます。

    • max-entries

      キャッシュに保持する必要があるエントリの制限を定義します。指定しない場合、デフォルト値は1000です。

  • paths

    保護するパスを指定します。この構成はオプションです。定義されていない場合、ポリシーエンフォーサーは、Keycloakでアプリケーションに定義したリソースをフェッチすることにより、すべてのパスを検出します。これらのリソースは、アプリケーションの一部のパスを表すURISで定義されています。

    • name

      特定のパスに関連付けるサーバー上のリソースの名前。pathと組み合わせて使用​​すると、ポリシーエンフォーサーはリソースのURISプロパティを無視し、代わりに指定したパスを使用します。

    • path

      (必須)アプリケーションのコンテキストパスに対する相対URI。このオプションを指定すると、ポリシーエンフォーサーは、同じ値のURIを持つリソースをサーバーにクエリします。現在、パスマッチングの非常に基本的なロジックがサポートされています。有効なパスの例を次に示します。

      • ワイルドカード:/*

      • サフィックス:/*.html

      • サブパス:/path/*

      • パスパラメータ:/resource/{id}

      • 完全一致:/resource

      • パターン:/{version}/resource、/api/{version}/resource、/api/{version}/resource/*

    • methods

      保護するHTTPメソッド(GET、POST、PATCHなど)と、それらがサーバー内の特定のリソースのスコープとどのように関連付けられているか。

      • method

        HTTPメソッドの名前。

      • scopes

        メソッドに関連付けられたスコープを持つ文字列の配列。スコープを特定のメソッドに関連付ける場合、保護されたリソース(またはパス)にアクセスしようとしているクライアントは、リストで指定されたすべてのスコープへのアクセス許可を付与するRPTを提供する必要があります。たとえば、スコープcreateを持つメソッドPOSTを定義した場合、RPTには、パスへのPOSTを実行するときにcreateスコープへのアクセスを許可する権限が含まれている必要があります。

      • scopes-enforcement-mode

        メソッドに関連付けられたスコープの施行モードを参照する文字列。値はALLまたはANYにできます。ALLの場合、そのメソッドを使用してリソースにアクセスするには、定義されたすべてのスコープを付与する必要があります。ANYの場合、そのメソッドを使用してリソースにアクセスするには、少なくとも1つのスコープを付与する必要があります。デフォルトでは、施行モードはALLに設定されています。

    • enforcement-mode

      ポリシーの施行方法を指定します。

      • ENFORCING(施行)

        (デフォルトモード)特定のリソースに関連付けられたポリシーがない場合でも、リクエストはデフォルトで拒否されます。

      • DISABLED(無効)

    • claim-information-point

      ポリシーで利用できるようにするために、解決してKeycloakサーバーにプッシュする必要がある1つ以上のクレームのセットを定義します。詳細については、クレーム情報ポイントを参照してください。

  • lazy-load-paths

    アダプターがアプリケーションのパスに関連付けられたリソースについてサーバーをフェッチする方法を指定します。trueの場合、ポリシーエンフォーサーは、リクエストされているパスに応じて、オンデマンドでリソースをフェッチします。この構成は、デプロイメント中にサーバーからすべてのリソースをフェッチしたくない場合(pathsを指定しなかった場合)、またはpathsのサブセットのみを定義し、他のリソースをオンデマンドでフェッチしたい場合に特に役立ちます。

  • http-method-as-scope

    スコープをHTTPメソッドにマッピングする方法を指定します。trueに設定すると、ポリシーエンフォーサーは、現在のリクエストからのHTTPメソッドを使用して、アクセスを許可する必要があるかどうかを確認します。有効にすると、Keycloakのリソースが保護している各HTTPメソッドを表すスコープに関連付けられていることを確認してください。

  • claim-information-point

    ポリシーで利用できるようにするために、解決してKeycloakサーバーにプッシュする必要がある1つ以上のグローバルクレームのセットを定義します。詳細については、クレーム情報ポイントを参照してください。

クレーム情報ポイント

クレーム情報ポイント(CIP)は、クレームを解決し、これらのクレームをKeycloakサーバーにプッシュして、アクセスコ​​ンテキストに関する詳細情報をポリシーに提供する責任があります。これらは、ポリシーエンフォーサーへの構成オプションとして定義して、次のようなさまざまなソースからクレームを解決できます。

  • HTTPリクエスト(パラメータ、ヘッダー、ボディなど)

  • 外部HTTPサービス

  • 構成で定義された静的な値

  • クレーム情報プロバイダーSPIを実装することにより、その他のソース

クレームをKeycloakサーバーにプッシュする場合、ポリシーは、ユーザーが誰であるかだけでなく、トランザクションの誰、何を、なぜ、いつ、どこで、そしてどちらに基づいてコンテキストとコンテンツを考慮に入れることによって決定を下すことができます。これはすべて、コンテキストベースの認可と、きめ細かい認可決定をサポートするためにランタイム情報を使用する方法に関するものです。

HTTPリクエストから情報を取得する

HTTPリクエストからクレームを抽出する方法を示すいくつかの例を次に示します。

keycloak.json
{
  "paths": [
    {
      "path": "/protected/resource",
      "claim-information-point": {
        "claims": {
          "claim-from-request-parameter": "{request.parameter['a']}",
          "claim-from-header": "{request.header['b']}",
          "claim-from-cookie": "{request.cookie['c']}",
          "claim-from-remoteAddr": "{request.remoteAddr}",
          "claim-from-method": "{request.method}",
          "claim-from-uri": "{request.uri}",
          "claim-from-relativePath": "{request.relativePath}",
          "claim-from-secure": "{request.secure}",
          "claim-from-json-body-object": "{request.body['/a/b/c']}",
          "claim-from-json-body-array": "{request.body['/d/1']}",
          "claim-from-body": "{request.body}",
          "claim-from-static-value": "static value",
          "claim-from-multiple-static-value": ["static", "value"],
          "param-replace-multiple-placeholder": "Test {keycloak.access_token['/custom_claim/0']} and {request.parameter['a']}"
        }
      }
    }
  ]
}

外部HTTPサービスから情報を取得する

外部HTTPサービスからクレームを抽出する方法を示すいくつかの例を次に示します。

keycloak.json
{
  "paths": [
    {
      "path": "/protected/resource",
      "claim-information-point": {
        "http": {
          "claims": {
            "claim-a": "/a",
            "claim-d": "/d",
            "claim-d0": "/d/0",
            "claim-d-all": [
              "/d/0",
              "/d/1"
            ]
          },
          "url": "http://mycompany/claim-provider",
          "method": "POST",
          "headers": {
            "Content-Type": "application/x-www-form-urlencoded",
            "header-b": [
              "header-b-value1",
              "header-b-value2"
            ],
            "Authorization": "Bearer {keycloak.access_token}"
          },
          "parameters": {
            "param-a": [
              "param-a-value1",
              "param-a-value2"
            ],
            "param-subject": "{keycloak.access_token['/sub']}",
            "param-user-name": "{keycloak.access_token['/preferred_username']}",
            "param-other-claims": "{keycloak.access_token['/custom_claim']}"
          }
        }
      }
    }
  ]
}

静的クレーム

keycloak.json
{
  "paths": [
    {
      "path": "/protected/resource",
      "claim-information-point": {
        "claims": {
          "claim-from-static-value": "static value",
          "claim-from-multiple-static-value": ["static", "value"]
        }
      }
    }
  ]
}

クレーム情報プロバイダーSPI

クレーム情報プロバイダーSPIは、組み込みプロバイダーのいずれも要件に対処するのに十分でない場合に、開発者がさまざまなクレーム情報ポイントをサポートするために使用できます。

たとえば、新しいCIPプロバイダーを実装するには、org.keycloak.adapters.authorization.ClaimInformationPointProviderFactoryClaimInformationPointProviderを実装し、アプリケーションのクラスパスにファイルMETA-INF/services/org.keycloak.adapters.authorization.ClaimInformationPointProviderFactoryも提供する必要があります。

org.keycloak.adapters.authorization.ClaimInformationPointProviderFactoryの例

public class MyClaimInformationPointProviderFactory implements ClaimInformationPointProviderFactory<MyClaimInformationPointProvider> {

    @Override
    public String getName() {
        return "my-claims";
    }

    @Override
    public void init(PolicyEnforcer policyEnforcer) {

    }

    @Override
    public MyClaimInformationPointProvider create(Map<String, Object> config) {
        return new MyClaimInformationPointProvider(config);
    }
}

すべてのCIPプロバイダーは、上記のMyClaimInformationPointProviderFactory.getNameメソッドで定義されているように、名前に関連付けられている必要があります。この名前は、policy-enforcer構成のclaim-information-pointセクションからの構成を実装にマッピングするために使用されます。

リクエストを処理するとき、ポリシーエンフォーサーはMyClaimInformationPointProviderFactory.createメソッドを呼び出して、MyClaimInformationPointProviderのインスタンスを取得します。呼び出されると、この特定のCIPプロバイダー(クレーム情報ポイント経由)に対して定義された構成はマップとして渡されます。

ClaimInformationPointProviderの例

public class MyClaimInformationPointProvider implements ClaimInformationPointProvider {

    private final Map<String, Object> config;

    public MyClaimInformationPointProvider(Map<String, Object> config) {
        this.config = config;
    }

    @Override
    public Map<String, List<String>> resolve(HttpFacade httpFacade) {
        Map<String, List<String>> claims = new HashMap<>();

        // put whatever claim you want into the map

        return claims;
    }
}

認可コンテキストを取得する

ポリシー施行が有効になっている場合、サーバーから取得した権限はorg.keycloak.AuthorizationContextを介して利用できます。このクラスは、権限を取得し、特定のリソースまたはスコープに対して権限が付与されたかどうかを確認するために使用できるいくつかのメソッドを提供します。

サーブレットコンテナで認可コンテキストを取得する

HttpServletRequest request = // obtain javax.servlet.http.HttpServletRequest
AuthorizationContext authzContext = (AuthorizationContext) request.getAttribute(AuthorizationContext.class.getName());
認可コンテキストは、サーバーによって行われ、返される決定をより細かく制御するのに役立ちます。たとえば、リソースまたはスコープに関連付けられた権限に応じてアイテムを非表示または表示する動的メニューを構築するために使用できます。
if (authzContext.hasResourcePermission("Project Resource")) {
    // user can access the Project Resource
}

if (authzContext.hasResourcePermission("Admin Resource")) {
    // user can access administration resources
}

if (authzContext.hasScopePermission("urn:project.com:project:create")) {
    // user can create new projects
}

AuthorizationContextは、Keycloak Authorization Servicesの主な機能の1つを表しています。上記の例から、保護されたリソースが、それらを管理するポリシーに直接関連付けられていないことがわかります。

ロールベースのアクセス制御(RBAC)を使用する同様のコードを考えてみましょう

if (User.hasRole('user')) {
    // user can access the Project Resource
}

if (User.hasRole('admin')) {
    // user can access administration resources
}

if (User.hasRole('project-manager')) {
    // user can create new projects
}

どちらの例も同じ要件に対処していますが、異なる方法で対処しています。RBACでは、ロールはリソースへのアクセスを暗黙的にのみ定義します。Keycloakを使用すると、RBAC、属性ベースのアクセス制御(ABAC)、またはその他のBACバリアントを使用しているかどうかにかかわらず、リソースに直接焦点を当てた、より管理しやすいコードを作成する機能が得られます。特定のリソースまたはスコープに対する権限があるか、またはその権限がないかのどちらかです。

ここで、セキュリティ要件が変更され、プロジェクトマネージャーに加えて、PMOも新しいプロジェクトを作成できると仮定します。

セキュリティ要件は変更されますが、Keycloakを使用すると、新しい要件に対処するためにアプリケーションコードを変更する必要はありません。アプリケーションがリソースおよびスコープ識別子に基づいている場合、認可サーバー内の特定のリソースに関連付けられた権限またはポリシーの構成のみを変更する必要があります。この場合、Project Resourceおよび/またはスコープurn:project.com:project:createに関連付けられた権限とポリシーが変更されます。

AuthorizationContextを使用して認可クライアントインスタンスを取得する

AuthorizationContextは、アプリケーションに構成されたKeycloak認可クライアントへの参照を取得するためにも使用できます

ClientAuthorizationContext clientContext = ClientAuthorizationContext.class.cast(authzContext);
AuthzClient authzClient = clientContext.getClient();

場合によっては、ポリシーエンフォーサーによって保護されているリソースサーバーが、認可サーバーによって提供されるAPIにアクセスする必要があります。AuthzClientインスタンスを所有している場合、リソースサーバーはサーバーと対話して、リソースを作成したり、特定の権限をプログラムで確認したりできます。

TLS/HTTPSの構成

サーバーがHTTPSを使用している場合は、ポリシーエンフォーサーが次のように構成されていることを確認してください

{
  "truststore": "path_to_your_trust_store",
  "truststore-password": "trust_store_password"
}

上記の構成により、認可クライアントへのTLS/HTTPSが有効になり、HTTPSスキームを使用してKeycloakサーバーにリモートでアクセスできるようになります。

Keycloakサーバーエンドポイントにアクセスする場合は、TLS/HTTPSを有効にすることを強くお勧めします。
このページの内容