Keycloak 政策執行器

在 Java 應用程式中使用 Keycloak 政策執行器

政策執行點 (PEP) 是一種設計模式,因此您可以用不同的方式實作它。Keycloak 提供了在不同平台、環境和程式語言中實作 PEP 所需的所有方法。Keycloak 授權服務提供 RESTful API,並利用 OAuth2 授權功能,使用集中式授權伺服器進行細粒度授權。

PEP overview

PEP 負責執行來自 Keycloak 伺服器的存取決策,這些決策是透過評估與受保護資源相關聯的政策來制定的。它在您的應用程式中充當過濾器或攔截器,以便根據這些決策授予的權限檢查是否可以滿足對受保護資源的特定請求。

Keycloak 提供內建支援,可讓 Java 應用程式啟用 Keycloak 政策執行器,並內建支援以保護符合 JakartaEE 的框架和網頁容器。如果您使用 Maven,則應為您的專案設定以下相依性

<dependency>
    <groupId>org.keycloak</groupId>
    <artifactId>keycloak-policy-enforcer</artifactId>
    <version>26.0.2</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,該 RPT 會授權存取清單中指定的所有範圍。例如,如果您定義了一個具有範圍createPOST方法,則 RPT 必須包含一個授權存取create範圍的權限,才能對該路徑執行 POST。

      • scopes-enforcement-mode

        一個字串,參考與方法關聯的範圍的執行模式。值可以是 ALLANY。如果為 ALL,則必須授予所有定義的範圍,才能使用該方法存取資源。如果為 ANY,則應至少授予一個範圍,才能使用該方法存取資源。預設情況下,執行模式設定為 ALL

    • enforcement-mode

      指定如何執行政策。

      • ENFORCING

        (預設模式)即使沒有與給定資源關聯的政策,預設也會拒絕請求。

      • DISABLED

    • claim-information-point

      定義一組或多個必須解析並推送至 Keycloak 伺服器的宣告,以便使這些宣告可供政策使用。有關詳細資訊,請參閱宣告資訊點

  • lazy-load-paths

    指定配接器應如何從伺服器擷取與應用程式中路徑關聯的資源。如果為 true,則政策執行器將根據請求的路徑按需擷取資源。當您不想在部署期間從伺服器擷取所有資源時(如果您沒有提供 paths),或者如果您僅定義了 paths 的子集並想按需擷取其他資源時,此組態特別有用。

  • http-method-as-scope

    指定應如何將範圍對應到 HTTP 方法。如果設定為 true,政策執行器將使用目前請求中的 HTTP 方法來檢查是否應授予存取權限。啟用後,請確保 Keycloak 中的資源與代表您正在保護的每個 HTTP 方法的範圍關聯。

  • claim-information-point

    定義一組或多個必須解析並推送至 Keycloak 伺服器的全域宣告,以便使這些宣告可供政策使用。有關詳細資訊,請參閱宣告資訊點

宣告資訊點

宣告資訊點 (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 取得。此類別提供多種方法,您可以使用這些方法來取得權限並確定是否已為特定資源或範圍授予權限。

在 Servlet 容器中取得授權內容

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 授權服務的主要功能之一。從上面的範例中,您可以看到受保護的資源並未與管理它們的政策直接關聯。

請考慮使用基於角色的存取控制 (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 時,無需變更您的應用程式程式碼來處理新的需求。一旦您的應用程式基於資源和範圍識別符號,您只需要變更授權伺服器中與特定資源相關聯的權限或政策的組態即可。在這種情況下,與專案資源和/或範圍 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。
本頁面內容