Keycloak中授權的實現

dax.net發表於2024-04-13

在Keycloak中實現授權,首先需要了解與授權相關的一些概念。授權,簡單地說就是某個(些)使用者或者某個(些)使用者組Policy),是否具有對某個資源Resource)具有某種操作Scope)的許可權Permission)。所以,授權是一種許可權管理,它建立在認證的基礎上:使用者首先要完成認證(Authentication),才能談授權(Authorization)。在討論認證與授權的文章或論壇裡,往往用Authn代表認證(Authentication),而用Authz代表授權(Authorization)。

Keycloak中的授權模型

在上面這段描述中,我已經將幾個重要的概念用黑體字標註了。或許會有這樣的疑問:使用者/使用者組不應該是User/Group嗎?在談授權的時候,至少也應該是角色(Role)吧,比如我們熟悉的基於角色的訪問控制(RBAC),裡面就是角色,怎麼會是策略(Policy)呢?在回答這個問題前,還是先看一下Keycloak中的授權模型:

Keycloak中授權的實現

這個模型中,包含了幾個重要的概念:

  1. 資源(Resource):資源是應用程式中能夠被訪問的物件。假設有個有關天氣預報的API,它的URL是http://localhost:5678/WeatherForecast,那麼,在這臺資源伺服器上,URI /WeatherForecast就是一個資源的地址,它表示一個跟天氣預報相關的API端點資源
  2. 操作(Scope):其實Scope並不翻譯為“操作”,這裡我使用“操作”來表示Scope,是因為在授權的場景中,Scope就是定義針對資源的一些“操作”。比如:對於上面的天氣預報API資源,我們可以有獲取資源的操作,也可以有更新資源的操作(比如,讓氣象員根據其它科學資料來調整某地的天氣預報)。於是,在定義Scope的時候,可以用“weatherforecast.read”、“weatherforecast.update”這樣的名字來命名
  3. 對於某個資源,它可以宣告自己所需要的操作,例如,在RESTful API中,/WeatherForecast這個API可以有讀取(read/HTTP GET)的操作,也可以有更新(update/HTTP PATCH)的操作,那麼,就可以在這個API資源上宣告weatherforecast.read和weatherforecast.update這兩個Scope
  4. 一個使用者組(Group)可以包含多個子組,一個組下可以有多個使用者(User),一個使用者又可以屬於多個使用者組。對於一個使用者或者一個組而言,它可以扮演多種角色(Role),而一個角色又可以被賦予多個使用者或者多個使用者組,這些都是耳熟能詳的RBAC授權的基本概念,就不多說明了
  5. 策略(Policy)可以理解為滿足某種條件的資源訪問者(可以是使用者或使用者組),所以,Policy定義的是條件:角色就是一種條件,表示“被賦予某種角色”的條件。基於角色的策略實現的訪問控制,就是RBAC。當然條件不僅僅只有角色,使用者滿足某個條件也可以成為一種策略,比如要求某個使用者年齡大於18歲。除此之外,策略是可以被聚合的,聚合策略的投票結果(允許還是拒絕),取決於被聚合的策略以及投票的方式(是所有被聚合策略都允許,結果才被允許,還是隻要有一個投票為“允許”,整個聚合策略的結果就是“允許”),比如要求使用者是年齡大於18歲(User Policy)的系統管理員(Role Policy)。上圖中只簡單列了幾個繼承於Policy的子類用以示意,Keycloak所支援的策略型別不止這些
  6. 許可權(Permission)是資源(Resource)或者操作(Scope)與策略(Policy)之間的關聯關係。在Keycloak中,許可權分為兩種:基於資源的許可權和基於操作的許可權。表達的語義是:符合某些策略的訪問者對指定的資源或者操作可以訪問

理解了這些概念後,在Keycloak中實現授權並不困難。

演練:在Keycloak中實現授權

還是以Weather API為例,設定這樣的業務場景:

  1. 服務供應商(Service Provider)釋出/WeatherForecast API供外部訪問
  2. 在企業應用(Client)裡有三個使用者:super,daxnet,nobody
  3. 在企業應用裡有兩個使用者組:administrators,users
  4. 在企業應用裡定義了兩個使用者角色:administrator,regular user
  5. super使用者同時屬於users和administrators組,daxnet屬於users組,nobody部署於任何組
  6. administrators組被賦予了administrator角色,users組被賦予了regular user角色
  7. 對於/WeatherForecast API,它支援兩種操作:GET /WeatherForecast,用以返回天氣預報資料;PATCH /WeatherForecast,用以調整天氣預報資料
  8. 擁有administrator角色的使用者/組,具有PATCH操作的許可權;擁有regular user角色但沒有administrator角色的使用者/組,具有GET操作的許可權;沒有任何角色的使用者,就沒有訪問/WeatherForecast API的許可權

這個業務場景也可以用下面的圖來表述:

Keycloak中授權的實現

首先,在Keycloak中新建一個名為aspnetcoreauthz的Realm,在這個Realm下,新建三個User,分別是super,daxnet和nobody;然後新建兩個Group:administrators和users,將super使用者放到administrators組和users組裡,並將daxnet使用者放入users組裡。

然後,新建一個名為weatherapiclient的Client,在weatherapiclient的頁面裡,點選Roles選項卡,建立兩個名為administrator和regular user的角色,然後回到Groups裡,選中administrators組,在Role mapping中,將administrator角色賦予該組:

Keycloak中授權的實現

用同樣的方法,將regular user角色賦予users組。

現在進入Authorization選項卡,點選Scopes選項卡,然後點選Create authorization scope按鈕:

Keycloak中授權的實現

在Create authorization scope頁面中,Name欄位輸入weather.read,用同樣的方法,新建另一個Scope,名稱為weather.update。然後點選Resources選項卡,並點選Create resource按鈕,建立API resource:

Keycloak中授權的實現

在Create resource頁面,新建名為weather-api的資源,填入如下欄位,然後點選Save按鈕儲存:

Keycloak中授權的實現

回到Authorization標籤頁,點選Policies標籤頁,點選Create client policy按鈕,在彈出的對話方塊中,選擇Role,表示需要建立一個基於角色的策略。在Create role policy頁面,新建一個名為require-admin-policy的策略,在Roles部分,點選Add roles按鈕,選擇weatherapiclient下的administrator角色,然後點選Save按鈕儲存:

Keycloak中授權的實現

用同樣的方法建立require-registered-user策略,並將regular user作為角色加入。接下來開始建立許可權實體(Permission)。在Authorization選項卡里,點選Permission選項卡,然後點選Create permission,然後選擇Create scope-based permission。在Create scope-based permission頁面,建立一個名為weather-view-permission的Permission,Authorization scopes選擇weather.read,Policies選擇require-registered-user,這裡的語義已經很明白了:執行weather.read操作,需要require-registered-user策略,也就是要讀取天氣預報資訊,就需要已註冊使用者。點選Save按鈕儲存即可。

Keycloak中授權的實現

用同樣的方法建立另一個名為weather-modify-permission的Permission,Authorization scopes為weather.update,Policies為require-admin-policy。

接下來,就可以測試許可權的設定是否正確了。仍然在Authorization選項卡下,點選Evaluate選項卡,在Identity Information部分,Users裡選擇super:

Keycloak中授權的實現

然後點選Evaluate按鈕,之後就可以看到,weather-modify-permission和weeather-view-permission均投票為Permit,表示該使用者具有兩者許可權:

Keycloak中授權的實現

如果點選Show authorization data,則在彈出的Authorization data對話方塊中,可以看到token裡已經包含了授權資訊(authorization Claim):

{
  "exp": 1712996185,
  "iat": 1712995885,
  "jti": "4f1178f2-5e8b-41e4-b726-da9120d77baa",
  "aud": "weatherapiclient",
  "sub": "44bbfc3a-16a0-499a-aae9-a2aa36219d33",
  "typ": "Bearer",
  "azp": "weatherapiclient",
  "session_state": "2b228dd4-38c8-4002-bc11-b35ecd109a63",
  "acr": "1",
  "allowed-origins": [
    "/*"
  ],
  "realm_access": {
    "roles": [
      "default-roles-aspnetcoreauthz",
      "offline_access",
      "uma_authorization"
    ]
  },
  "resource_access": {
    "weatherapiclient": {
      "roles": [
        "administrator",
        "regular user"
      ]
    },
    "account": {
      "roles": [
        "manage-account",
        "manage-account-links",
        "view-profile"
      ]
    }
  },
  "authorization": {
    "permissions": [
      {
        "scopes": [
          "weather.update",
          "weather.read"
        ],
        "rsid": "f6fd1d6f-3bfd-44a1-a6fb-a1fb49769ac9",
        "rsname": "weather-api"
      }
    ]
  },
  "scope": "email profile",
  "sid": "2b228dd4-38c8-4002-bc11-b35ecd109a63",
  "email_verified": false,
  "name": "Admin User",
  "groups": [
    "/administrators",
    "/users"
  ],
  "preferred_username": "super",
  "given_name": "Admin",
  "family_name": "User",
  "email": "super@abc.com"
}

換一個使用者,如果選擇daxnet,可以看到,weather-view-permission為Permit,而weather-modify-permission為Deny:

Keycloak中授權的實現

再將使用者換為nobody測試一下,發現兩個Permission的結果都為Deny:

Keycloak中授權的實現

透過token API端點請求授權資訊

要使用OpenID Connect的token API端點獲得某個使用者的授權資訊,需要首先得到Bearer token:

Keycloak中授權的實現

然後,使用這個Bearer token,再次呼叫token API,注意此時的grant_type為 urn:ietf:params:oauth:grant-type:uma-ticket,audience為Client ID,即weatherapiclient:

Keycloak中授權的實現

在jwt.io中解碼第二步生成的這個access_token,就可以拿到授權資訊了:

Keycloak中授權的實現

相關文章