PHP-Casbin

PHP-Casbin

基礎知識

概述

Casbin是一個強大的、高效的開源訪問控制框架,其權限管理機制支持多種訪問控制模型。

Casbin是什么?

Casbin可以做到:

  1. 支持自定義請求的格式,默認的請求格式為{subject, object, action}。
  2. 具有訪問控制模型model和策略policy兩個核心概念。
  3. 支持RBAC中的多層角色繼承,不止主體可以有角色,資源也可以具有角色。
  4. 支持超級用戶,如 rootAdministrator,超級用戶可以不受授權策略的約束訪問任意資源。
  5. 支持多種內置的操作符,如 keyMatch,方便對路徑式的資源進行管理,如 /foo/bar 可以映射到 /foo*

Casbin不能做到:

  1. 身份認證 authentication(即驗證用戶的用戶名、密碼),casbin只負責訪問控制。應該有其他專門的組件負責身份認證,然后由casbin進行訪問控制,二者是相互配合的關系。
  2. 管理用戶列表或角色列表。 Casbin 認為由項目自身來管理用戶、角色列表更為合適, 用戶通常有他們的密碼,但是 Casbin 的設計思想并不是把它作為一個存儲密碼的容器。 而是存儲RBAC方案中用戶和角色之間的映射關系。

安裝

composer require casbin/casbin

開始使用

require_once './vendor/autoload.php';

use Casbin\Enforcer;

$e = new Enforcer("path/to/model.conf", "path/to/policy.csv");

你可以在初始化Enforcer實例時,使用數(shù)據(jù)庫代替文件,相關說明,后面將會提到

進行訪問控制

$sub = "alice"; // 角色名
$obj = "data1"; // 訪問的資源
$act = "read"; // 訪問的權限

// 驗證該角色是否有訪問某資源的權限
if ($e->enforce($sub, $obj, $act) === true) {
    // 允許訪問
} else {
    // 禁止訪問
}

Casbin也提供了API用于權限管理,例如:你可以獲取分配給某個角色的所有權限

$roles = $e->getRolesForUser("alice");

查看 Management APIRBAC API 獲取更多的用法

請參考測試用例來了解更多的用法

工作原理

在 Casbin 中, 訪問控制模型被抽象為基于 PERM (Policy, Effect, Request, Matcher) 的一個文件。 因此,切換或升級項目的授權機制與修改配置一樣簡單。 您可以通過組合可用的模型來定制您自己的訪問控制模型。 例如,您可以在一個model中獲得RBAC角色和ABAC屬性,并共享一組policy規(guī)則。

Policy:策略 Effect:作用范圍 Request:請求 Matcher:匹配器

Casbin中最基本、最簡單的model是ACL。ACL中的model CONF為:

[request_definition]
r = sub, obj, act

[policy_definition]
p = sub, obj, act

[policy_effect]
e = some(where (p.eft == allow))

[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act

ACL model的示例policy如下:

p, alice, data1, read
p, bob, data2, write

這表示:

  • alice可以讀取data1
  • bob可以編寫data2

對于過長的單行配置,您也可以通過在結尾處添加“\”進行斷行:

# 匹配器
[matchers]
m = r.sub == p.sub && r.obj == p.obj \ 
  && r.act == p.act

此外,對于 ABAC,您在可以在 Casbin golang 版本中嘗試下面的 (jCasbin 和 Node-Casbin 尚不支持)操作:

# 匹配器
[matchers]
m = r.obj == p.obj && r.act == p.act || r.obj in ('data2', 'data3')

但是你應確保數(shù)組的長度大于 1,否則的話將會導致 panic 。

對于更多操作,你可以查看govaluate。

Model

支持的Models

  1. ACL (Access Control List, 訪問控制列表)
  2. 具有的超級用戶ACL
  3. 沒有用戶的 ACL: 對于沒有身份驗證或用戶登錄的系統(tǒng)尤其有用。
  4. 沒有資源的 ACL: 某些場景可能只針對資源的類型, 而不是單個資源, 諸如 write-article, read-log等權限。 它不控制對特定文章或日志的訪問。
  5. RBAC (基于角色的訪問控制)
  6. 支持資源角色的RBAC: 用戶和資源可以同時具有角色 (或組)。
  7. 支持域/租戶的RBAC: 用戶可以為不同的域/租戶設置不同的角色集。
  8. ABAC (基于屬性的訪問控制): 支持利用resource.Owner這種語法糖獲取元素的屬性。
  9. RESTful: 支持路徑, 如 /res/*, /res/: id 和 HTTP 方法, 如 GET, POST, PUT, DELETE。
  10. 拒絕優(yōu)先: 支持允許和拒絕授權, 拒絕優(yōu)先于允許。
  11. 優(yōu)先級: 策略規(guī)則按照先后次序確定優(yōu)先級,類似于防火墻規(guī)則。

例子

訪問控制模型 Model 文件 Policy 文件
ACL basic_model.conf basic_policy.csv
具有超級用戶的ACL basic_model_with_root.conf basic_policy.csv
沒有用戶的ACL basic_model_without_users.conf basic_policy_without_users.csv
沒有資源的ACL basic_model_without_resources.conf basic_policy_without_resources.csv
RBAC rbac_model.conf rbac_policy.csv
支持資源角色的RBAC rbac_model_with_resource_roles.conf rbac_policy_with_resource_roles.csv
支持域/租戶的RBAC rbac_model_with_domains.conf rbac_policy_with_domains.csv
ABAC abac_model.conf
RESTful keymatch_model.conf keymatch_policy.csv
拒絕優(yōu)先 rbac_model_with_deny.conf rbac_policy_with_deny.csv
優(yōu)先級 priority_model.conf priority_policy.csv

Model語法

  • Model CONF 至少應包含四個部分: [request_definition], [policy_definition], [policy_effect], [matchers]。
  • 如果 model 使用 RBAC, 還需要添加[role_definition]部分。
  • 一個Model CONF可以包含注釋。注釋以#開頭

request_definition:請求定義 policy_definition:策略定義 policy_effect:策略作用范圍 matchers:匹配器 role_definition:角色定義

Request定義

[request_definition] 部分用于request的定義,它明確了 $e->enforce($sub, $obj, $act) 函數(shù)中參數(shù)的含義。

# 請求定義
[request_definition]
r = sub, obj, act

sub, obj, act 表示經(jīng)典三元組: 訪問實體 (Subject),訪問資源 (Object) 和訪問方法 (Action)。 但是, 你可以自定義你自己的請求表單, 如果不需要指定特定資源,則可以這樣定義 sub、act ,或者如果有兩個訪問實體, 則為 sub、sub2、obj、act

Policy定義

[policy_definition] 部分是對policy的定義,以下文的 model 配置為例:

# 策略定義
[policy_definition]
p = sub, obj, act
p2 = sub, act

這些是我們對policy規(guī)則的具體描述

p, alice, data1, read
p2, bob, write-all-objects

policy部分的每一行稱之為一個策略規(guī)則, 每條策略規(guī)則通常以形如p, p2policy type開頭。 如果存在多個policy定義,那么我們會根據(jù)前文提到的policy type與具體的某條定義匹配。 上面的policy的綁定關系將會在matcher中使用, 羅列如下:

(alice, data1, read) -> (p.sub, p.obj, p.act)
(bob, write-all-objects) -> (p2.sub, p2.act)

注1: 當前只支持形如 p的單個policy定義, 形如p2 類型的尚未支持。 通常情況下, 用戶無需使用多個 policy 定義, 如果您有其他情形的policy定義訴求,請在 https://github.com/casbin/casbin/issues/new 提出issue告知我們。

注2: policy定義中的元素始終被視為字符串(string)對待, 如果您對此有疑問,請移步https://github.com/casbin/casbin/issues/113

Policy effect定義

[policy_effect] 部分是對policy生效范圍的定義, 原語定義了當多個policy rule同時匹配訪問請求request時,該如何對多個決策結果進行集成以實現(xiàn)統(tǒng)一決策。 以下示例展示了一個只有一條規(guī)則生效,其余都被拒絕的情況:

# 策略生效范圍
[policy_effect]
e = some(where (p.eft == allow))

該Effect原語表示如果存在任意一個決策結果為allow的匹配規(guī)則,則最終決策結果為allow,即allow-override。 其中p.eft 表示策略規(guī)則的決策結果,可以為allow 或者deny,當不指定規(guī)則的決策結果時,取默認值allow 。 通常情況下,policy的p.eft默認為allow, 因此前面例子中都使用了這個默認值。

這是另一個policy effect的例子:

# 策略生效范圍
[policy_effect]
e = !some(where (p.eft == deny))

該Effect原語表示不存在任何決策結果為deny的匹配規(guī)則,則最終決策結果為allow ,即deny-override。 some 量詞判斷是否存在一條策略規(guī)則滿足匹配器。 any 量詞則判斷是否所有的策略規(guī)則都滿足匹配器 (此處未使用)。

policy effect還可以利用邏輯運算符進行連接:

# 策略生效范圍
[policy_effect]
e = some(where (p.eft == allow)) && !some(where (p.eft == deny))

該Effect原語表示當至少存在一個決策結果為allow的匹配規(guī)則,且不存在決策結果為deny的匹配規(guī)則時,則最終決策結果為allow。 這時allow授權和deny授權同時存在,但是deny優(yōu)先。

Matchers

[matchers] 原語定義了策略規(guī)則如何與訪問請求進行匹配的匹配器,其本質上是布爾表達式,可以理解為Request、Policy等原語定義了關于策略和請求的變量,然后將這些變量代入Matcher原語中求值,從而進行策略決策。

# 匹配器
[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act

這是一個簡單的例子,該Matcher原語表示,訪問請求request中的subject、object、action三元組應與策略規(guī)則policy rule中的subject、object、action三元組分別對應相同。

Matcher原語支持+、 -、 *、 /等算數(shù)運算符,==,、!=、 >、 <等關系運算符以及&& (與)、|| (或)、 ! (非)等邏輯運算符。

: 雖然可以像其他原語一樣的編寫多個類似于 m1, m2 的matcher, 但是當前我們只支持一個有效的 matcher m。 通常情況下,您可以在一個matcher中使用上文提到的邏輯運算符來實現(xiàn)復雜的邏輯判斷, 因而我們認為目前不需要支持多個matcher。 如果您對此有疑問,請告知我們(https://github.com/casbin/casbin/issues)。

matcher中的函數(shù)

matcher的強大與靈活之處在于您甚至可以在matcher中定義函數(shù),這些函數(shù)可以是內置函數(shù)或自定義的函數(shù)。當前支持的內置函數(shù)如下:

函數(shù) 釋義 示例
keyMatch(arg1, arg2) 參數(shù) arg1 是一個 URL 路徑,例如 /alice_data/resource1,參數(shù) arg2 可以是URL路徑或者是一個 * 模式,例如 /alice_data/*。此函數(shù)返回 arg1是否與 arg2 匹配。 keymatch_model.conf/keymatch_policy.csv
keyMatch2(arg1, arg2) 參數(shù) arg1 是一個 URL 路徑,例如 /alice_data/resource1,參數(shù) arg2 可以是 URL 路徑或者是一個 : 模式,例如/alice_data/:resource。此函數(shù)返回 arg1 是否與 arg2 匹配。 keymatch2_model.conf/keymatch2_policy.csv
regexMatch(arg1, arg2) arg1 可以是任何字符串。arg2 是一個正則表達式。它返回 arg1 是否匹配 arg2。 keymatch_model.conf/keymatch_policy.csv
ipMatch(arg1, arg2) arg1 是一個 IP 地址, 如 192.168.2.123。arg2 可以是 IP 地址或 CIDR, 如 192.168.2. 0/24。它返回 arg1 是否匹配 arg2。 ipmatch_model.conf/ipmatch_policy.csv

如何添加自定義函數(shù)(GO語言示例)

首先準備好一個有幾個參數(shù)和一個布爾值返回值的函數(shù):

func KeyMatch(key1 string, key2 string) bool {
    i := strings.Index(key2, "*")
    if i == -1 {
        return key1 == key2
    }

    if len(key1) > i {
        return key1[:i] == key2[:i]
    }
    return key1 == key2[:i]
}

然后用 interface{} 類型包裝此函數(shù):

func KeyMatchFunc(args ...interface{}) (interface{}, error) {
    name1 := args[0].(string)
    name2 := args[1].(string)

    return (bool)(KeyMatch(name1, name2)), nil
}

最后, 將該函數(shù)注冊到 Casbin enforcer:

e.AddFunction("my_func", KeyMatchFunc)

現(xiàn)在, 您可以像下面這樣使用 model 中的函數(shù):

# 匹配器
[matchers]
m = r.sub == p.sub && my_func(r.obj, p.obj) && r.act == p.act

RBAC

角色定義

[role_definition] 是RBAC角色繼承關系的定義。 Casbin 支持 RBAC 系統(tǒng)的多個實例, 例如, 用戶可以具有角色及其繼承關系, 資源也可以具有角色及其繼承關系。 這兩個 RBAC 系統(tǒng)不會互相干擾。

此部分是可選的。 如果在模型中不使用 RBAC 角色, 則省略此部分。

# 角色定義
[role_definition]
g = _, _
g2 = _, _

上述角色定義表明, g 是一個 RBAC系統(tǒng), g2 是另一個 RBAC 系統(tǒng)。 _, _表示角色繼承關系的前項和后項,即前項繼承后項角色的權限。 一般來講,如果您需要進行角色和用戶的綁定,直接使用g 即可。 當您需要表示角色(或者組)與用戶和資源的綁定關系時,可以使用gg2 這樣的表現(xiàn)形式。 請參見 rbac_modelrbac_model_with_resource_roles 的示例。

在Casbin里,我們以policy表示中實際的用戶角色映射關系 (或是資源-角色映射關系),例如:

p, data2_admin, data2, read
g, alice, data2_admin

這意味著 alice 是角色 data2_admin的一個成員。 alice 在這里可以是用戶、資源或角色。 Cabin 只是將其識別為一個字符串。

接下來在matcher中,應該像下面的例子一樣檢查角色信息:

# 匹配器
[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act

它表示請求中的sub應該在策略中定義了sub角色。

有幾個注意事項:

  1. Casbin 只存儲用戶角色的映射關系。
  2. Cabin 沒有驗證用戶是否是有效的用戶,或者角色是一個有效的角色。 這應該通過認證來解決。
  3. RBAC 系統(tǒng)中的用戶名稱和角色名稱不應相同。因為Casbin將用戶名和角色識別為字符串, 所以當前語境下Casbin無法得出這個字面量到底指代用戶 alice 還是角色 alice。 這時,使用明確的 role_alice ,問題便可迎刃而解。
  4. 假設A具有角色 BB 具有角色 C,并且 A 有角色 C。 這種傳遞性在當前版本會造成死循環(huán)。

角色層次

Casbin 的 RBAC 支持 RBAC1 的角色層次結構功能,如果 alice具有role1, role1具有role2,則 alice 也將擁有 role2 并繼承其權限。

下面是一個稱為層次結構級別的概念。 因此, 此示例的層次結構級別為2。 對于Casbin中的內置角色管理器, 可以指定最大層次結構級別。 默認值為10。 這意味著終端用戶 alice 只能繼承10個級別的角色。

// GO語言示例
// NewRoleManager重寫了默認角色管理RoleManger的構造方法
func NewRoleManager(maxHierarchyLevel int) rbac.RoleManager {
    rm := RoleManager{}
    rm.allRoles = &sync.Map{}
    rm.maxHierarchyLevel = maxHierarchyLevel
    rm.hasPattern = false

    return &rm
}

如何區(qū)分用戶和角色?

在RBAC中,Casbin不對用戶和角色進行區(qū)分。 它們都被視為字符串。 如果你只使用單層的RBAC模型(角色不會成為另一個角色的成員)。 可以使用 e.GetAllSubjects() 獲取所有用戶,e.GetAllRoles() 獲取所有角色。 它們會為規(guī)則 g, u, r 分別列出所有的 ur

但在使用多層RBAC模型時(帶有角色繼承),你的應用不會記錄一個名字(字符串)是用戶還是角色,或者用戶和角色有相同的名字。 可以給角色加上像 role::admin 的前綴再傳遞到Casbin中。 由此可以通過查看前綴來區(qū)分用戶和角色。

如何查詢隱性角色或權限?

當用戶通過RBAC層次結構繼承角色或權限,而不是直接在策略規(guī)則中分配它們時,我們將這種類型的分配稱為 implicit。 要查詢這種隱式關系,需要使用以下兩個api: GetImplicitRolesForUser()以及 GetImplicitPermissionsForUser 替代GetRolesForUser() 以及 GetPermissionsForUser. 有關詳情,請參閱 this GitHub issue。

在 RBAC 中使用模式匹配

有時,您希望將具有特定模式的某些subjects(或objects)自動授予某個角色。 RBAC中的模式匹配函數(shù)可以幫助做到這一點。 模式匹配函數(shù)與前一個函數(shù)共享相同的參數(shù)和返回值:matcher function。

我們知道RBAC在matcher中通常表示為g(r.sub, p.sub)。 然后我們將使用以下策略:

p, alice, book_group, read
g, /book/1, book_group
g, /book/2, book_group

所以 alice可以讀所有的book,包括 book 1book 2。 但是可能有成千上萬個book,使用 g 策略規(guī)則將每一個book添加到book角色(或組)是非常單調乏味的。

但是使用模式匹配函數(shù),您可以只用一行代碼編寫策略:

g, /book/:id, book_group

Casbin會自動將 /book/1/book/2匹配成模式/book/:id。 您只需要向強制程序注冊該功能,如:

// GO語言示例
e.rm.(*defaultrolemanager.RoleManager).AddMatchingFunc("KeyMatch2", util.KeyMatch2)

您可以在這里看到完整的示例 :here。

值得注意的是:

  1. 只有g種的第一參數(shù) (aka 用戶) 支持模式函數(shù)。 您正在使用第三個參數(shù)(domain),目前不支持。

角色管理器

角色管理器用于管理Casbin中的RBAC角色層次結構(用戶角色映射)。 角色管理器可以從Casbin策略規(guī)則或外部源(如LDAP、Okta、Auth0、Azure AD等)檢索角色數(shù)據(jù)。 我們支持角色管理器的不同實現(xiàn)。 為了保持代碼輕量級,我們沒有把角色管理器代碼放在主庫中(默認的角色管理器除外)。 下面提供了Casbin角色管理器的完整列表。 歡迎任何第三方對角色manager進行新的貢獻,如果有請告知我們,我們將把它放在這個列表中:

角色管理器 作者 描述
Default Role Manager (built-in) Casbin 支持存儲在Casbin策略中的角色層次結構
Session Role Manager EDOMO Systems 支持存儲在Casbin策略中的角色層次結構,以及基于時間范圍的會話
Okta Role Manager Casbin 支持存儲在Okta中的角色層次結構
Auth0 Role Manager Casbin 支持存儲在 Auth0's Authorization Extension 授權擴展名中的角色層次結構

對于開發(fā)人員:所有角色manager必須實現(xiàn) RoleManager接口。 Session Role Manager可以用作參考實現(xiàn)。

RBAC with Domains

域租戶的角色定義

在Casbin中的RBAC角色可以是全局或是基于特定于域的。 特定域的角色意味著當用戶處于不同的域/租戶群體時,用戶所表現(xiàn)的角色也不盡相同。 這對于像云服務這樣的大型系統(tǒng)非常有用,因為用戶通常分屬于不同的租戶群體。

域/租戶的角色定義應該類似于:

# 角色定義
[role_definition]
g = _, _, _

第三個 _ 表示域/租戶的名稱, 此部分不應更改。

然后,策略可以是:

p, admin, tenant1, data1, read
p, admin, tenant2, data2, read

g, alice, admin, tenant1
g, alice, user, tenant2

該實例表示tenant1的域內角色admin 可以讀取data1, alicetenant1域中具有admin角色,但在tenant2域中具有user角色, 所以alice可以有讀取data1的權限。 同理,因為alice不是tenant2admin,所以她訪問不了data2

接下來在matcher中,應該像下面的例子一樣檢查角色信息:

# 匹配器
[matchers]
m = g(r.sub, p.sub, r.dom) && r.dom == p.dom && r.obj == p.obj && r.act == p.act

更多示例參見: rbac_with_domains_model.conf。

ABAC

什么是ABAC模式?

ABAC是 基于屬性的訪問控制,可以使用主體、客體或動作的屬性,而不是字符串本身來控制訪問。 您之前可能就已經(jīng)聽過 XACML ,是一個復雜的 ABAC 訪問控制語言。 與XACML相比,Casbin的ABAC非常簡單: 在ABAC中,可以使用struct(或基于編程語言的類實例) 而不是字符串來表示模型元素。

例如,ABAC的官方實例如下:

# 請求定義
[request_definition]
r = sub, obj, act

# 政策定義
[policy_definition]
p = sub, obj, act

# 政策應用范圍
[policy_effect]
e = some(where (p.eft == allow))

# 匹配器
[matchers]
m = r.sub == r.obj.Owner

我們使用 r.obj.所有者 代替 r.obj matcher。 在 Enforce() 函數(shù)中傳遞的 r.obj 函數(shù)是結構或類實例,而不是字符串。 Casbin將使用映像來檢索 obj結構或類中的成員變量。

這里是 r.obj construction 或 class 的定義:

// GO語言示例
type testResource struct {
    Name  string
    Owner string
}

如何使用ABAC?

簡單地說,要使用ABAC,您需要做兩件事:

  1. 在模型匹配器中指定屬性。
  2. 將元素的結構或類實例作為Casbin的Enforce() 的參數(shù)傳入。

Note:

  1. 目前,僅請求元素,例如 r.such、[ r.obj,] r.action 等等支持ABAC的元素。 您不能在策略元素上使用它,比如p.sub,因為在Casbin的策略中沒有定義結構或者類。
  2. 您可以在一個matcher中使用多個ABAC屬性,例如:m = r.sub.Domain == r.obj.Domain。

存儲

Model存儲

與 policy 不同,model 只能加載,不能保存。 因為我們認為 model 不是動態(tài)組件,不應該在運行時進行修改,所以我們沒有實現(xiàn)一個 API 來將 model 保存到存儲中。

但是,好消息是,我們提供了三種等效的方法來靜態(tài)或動態(tài)地加載模型:

從 .CONF 文件中加載 model

當你向 Casbin 團隊尋求幫助時,他們會給你這個 Casbin 最常用的方法,此方法對于初學者來說很容易理解并且便于分享。

.CONF文件的內容 examples/rbac_model.conf

[request_definition]
r = sub, obj, act

[policy_definition]
p = sub, obj, act

[role_definition]
g = _, _

[policy_effect]
e = some(where (p.eft == allow))

[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act

接著你可以加載模型文件如下:

// GO語言示例
e := casbin.NewEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")

從代碼加載 model

型可以從代碼中動態(tài)初始化,不需要使用 .CONF。下面是RBAC模型的一個例子:

// GO語言示例
// 初始化model
m := model.NewModel()
m.AddDef("r", "r", "sub, obj, act")
m.AddDef("p", "p", "sub, obj, act")
m.AddDef("g", "g", "_, _")
m.AddDef("e", "e", "some(where (p.eft == allow))")
m.AddDef("m", "m", "g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act")

// 使用自己的 adapter 替換。
a := persist.NewFileAdapter("examples/rbac_policy.csv")

// 創(chuàng)建一個 enforcer。
e := casbin.NewEnforcer(m, a)

從字符串加載的 model

或者您可以從多行字符串加載整個模型文本。這種方法的優(yōu)點是您不需要維護模型文件。

// GO語言示例
// Initialize the model from a string.
text :=
`
[request_definition]
r = sub, obj, act

[policy_definition]
p = sub, obj, act

[role_definition]
g = _, _

[policy_effect]
e = some(where (p.eft == allow))

[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
`
m := NewModel(text)

// Load the policy rules from the .CSV file adapter.
// Replace it with your adapter to avoid files.
a := persist.NewFileAdapter("examples/rbac_policy.csv")

// Create the enforcer.
e := casbin.NewEnforcer(m, a)

Policy存儲

在Casbin中,策略存儲作為adapter實現(xiàn)。請參照:</docs/en/adapters>。

Policy Subset Loading

一些adapter支持過濾策略管理。 這意味著Casbin加載的策略是基于給定過濾器的存儲策略的子集。 當解析整個策略成為性能瓶頸時,這將會允許在大型多租戶環(huán)境中有效地執(zhí)行策略。

要使用支持的adapter處理過濾后的策略,只需調用 LoadFilteredPolicy 方法。 過濾器參數(shù)的有效格式取決于所用的適配器。 為了防止意外數(shù)據(jù)丟失,當策略已經(jīng)加載, SavePolicy 方法會被禁用。

例如,下面的代碼片段使用內置的過濾文件adapter和帶有域的RBAC模型。 在本例中,過濾器將策略限制為單個域。 除 "domain1" 以外的任何域策略行被忽略:

// GO語言示例
import "github.com/casbin/casbin"

enforcer := casbin.NewEnforcer()

adapter := fileadapter.NewFilteredAdapter("examples/rbac_with_domains_policy.csv")
enforcer.InitWithAdapter("examples/rbac_with_domains_model.conf", adapter)

filter := &fileadapter.Filter{
    P: []string{"", "domain1"},
    G: []string{"", "", "domain1"},
}
enforcer.LoadFilteredPolicy(filter)

// The loaded policy now only contains the entries pertaining to "domain1".

Extensions

Adapters

在Casbin中,策略存儲作為adapter(Casbin的中間件) 實現(xiàn)。 Casbin用戶可以使用adapte從存儲中加載策略規(guī)則 (aka LoadPolicy()) 或者將策略規(guī)則保存到其中 (aka SavePolicy())。 為了保持代碼輕量級,我們沒有把adapte代碼放在主庫中。

目前支持的adapter列表

Casbin角色管理器的完整列表如下所示。 歡迎任何第三方對adapter進行新的貢獻,如果有請通知我們,我們將把它放在這個列表中:)

適配器 類型 作者 自動保存 描述
File Adapter (built-in) File Casbin ? For .CSV (Comma-Separated Values) files
Database Adapter ORM Casbin ? MySQL, PostgreSQL, SQLite, Microsoft SQL Server are supported by techone/database
Zend Db Adapter ORM Casbin ? MySQL, PostgreSQL, SQLite, Oracle, IBM DB2, Microsoft SQL Server, Other PDO Driver are supported by zend-db
Doctrine DBAL Adapter(Recommend) ORM Casbin ? Powerful PHP database abstraction layer (DBAL) with many features for database schema introspection and management.
Medoo Adapter ORM Casbin ? Medoo is a lightweight PHP Database Framework to Accelerate Development, supports all SQL databases, including MySQL, MSSQL, SQLite, MariaDB, PostgreSQL, Sybase, Oracle and more.

這里有一些你需要知道的事情:

  1. 如果使用顯式或隱式adapter調用casbin.NewEnforcer(),策略將自動加載。
  2. 可以調用e.LoadPolicy() 來從存儲中重新加載策略規(guī)則。
  3. 如果adapter不支持Auto-Save特性,則在添加或刪除策略時不能將策略規(guī)則自動保存回存儲器。 您可以手動調用SavePolicy() 來保存所有策略規(guī)則。

例子

這里我門提供了幾個例子:

文件適配器 (內置)

下面展示了如何對內置的文件適配器進行初始化:

use Casbin\Enforcer;

$e = new Enforcer('examples/basic_model.conf', 'examples/basic_policy.csv');

同樣的:

use Casbin\Enforcer;
use Casbin\Persist\Adapters\FileAdapter;

$a = new FileAdapter('examples/basic_policy.csv');
$e = new Enforcer('examples/basic_model.conf', $a);

MySQL 適配器

下面展示了如何初始化一個MySQL適配器

// https://github.com/php-casbin/dbal-adapter

use Casbin\Enforcer;
use CasbinAdapter\DBAL\Adapter as DatabaseAdapter;

$config = [
    // driver的可選值:
    // pdo_mysql,pdo_sqlite,pdo_pgsql,pdo_oci (unstable),pdo_sqlsrv,pdo_sqlsrv,
    // mysqli,sqlanywhere,sqlsrv,ibm_db2 (unstable),drizzle_pdo_mysql
    'driver'     => 'pdo_mysql', 
    'host' => '127.0.0.1',
    'dbname' => 'test',
    'user' => 'root',
    'password' => '',
    'port' => '3306',
];

$a = DatabaseAdapter::newAdapter($config);
$e = new Enforcer('examples/basic_model.conf', $a);

使用自建的adapter

你可以使用自定義的適配器,例如:

// GO語言示例
import (
    "github.com/casbin/casbin"
    "github.com/your-username/your-repo"
)

a := yourpackage.NewAdapter(params)
e := casbin.NewEnforcer("examples/basic_model.conf", a)

在運行時進行加載或保存配置信息

你也許希望重新加載模型,重新加載策略或者在初始化后保存策略

// GO語言示例
// 從模型配置文件中重新加載模型
e.LoadModel()

// 從文件或數(shù)據(jù)庫中重新加載策略
e.LoadPolicy()

// Save the current policy (usually after changed with Casbin API) back to file/database.
// 將當前策略保存到文件或數(shù)據(jù)庫(通常在使用Casbin API之后)
e.SavePolicy()

自動保存

有一個稱為適配器自動保存的功能。當一個適配器支持自動保存功能時,就意味著它能夠支持從存儲器中添加一條策略或刪除一條策略。這與savePolicy()不同,后者將刪除存儲中的所有策略規(guī)則,并將所有策略規(guī)則從casbin Enforcer保存到存儲中。因此,當策略規(guī)則的數(shù)量很大時,它可能會遇到性能問題。

當適配器支持自動保存時,可以通過enforcer.enableautosave()函數(shù)切換此選項。默認情況下,該選項處于啟用狀態(tài)(如果適配器支持它)。

需要注意的是:

  1. Auto-Save 特性是可選的。 Adapter可以選擇是否實現(xiàn)它。
  2. Auto-Save 只在Casbin enforcer使用的adapter支持它時才有效。
  3. 查看上述adapter列表中的 AutoSave列,查看adapter是否支持 Auto-Save。

這里有一個關于自動保存的例子:

// GO語言示例
import (
    "github.com/casbin/casbin"
    "github.com/casbin/xorm-adapter"
    _ "github.com/go-sql-driver/mysql"
)

// By default, the AutoSave option is enabled for an enforcer.
a := xormadapter.NewAdapter("mysql", "mysql_username:mysql_password@tcp(127.0.0.1:3306)/")
e := casbin.NewEnforcer("examples/basic_model.conf", a)

// Disable the AutoSave option.
e.EnableAutoSave(false)

// Because AutoSave is disabled, the policy change only affects the policy in Casbin enforcer,
// it doesn't affect the policy in the storage.
e.AddPolicy(...)
e.RemovePolicy(...)

// Enable the AutoSave option.
e.EnableAutoSave(true)

// Because AutoSave is enabled, the policy change not only affects the policy in Casbin enforcer,
// but also affects the policy in the storage.
e.AddPolicy(...)
e.RemovePolicy(...)

更多示例,請參考:https://github.com/casbin/xorm-adapter/blob/master/adapter_test.go

如何編寫 Adapter

所有適配器需要至少實現(xiàn)兩個適配器接口中的兩個方法:LoadPolicy(model model.Model) error and SavePolicy(model model.Model) error

其他三個功能是可選的。如果適配器支持自動保存功能,則應該實現(xiàn)它們。

方法 類型 描述
LoadPolicy() 強制的 從存儲中加載所有策略規(guī)則
SavePolicy() 強制的 將所有策略規(guī)則保存到存儲中
AddPolicy() 可選擇的 向存儲中添加策略規(guī)則
RemovePolicy() 可選擇的 從存儲中刪除策略規(guī)則
RemoveFilteredPolicy() 可選擇的 從存儲中刪除匹配篩選器的策略規(guī)則

注意:如果適配器不支持自動保存功能,它應該為三個可選功能提供一個空實現(xiàn)。如果您不提供它,編譯器將會報錯。下面是一個例子:

// GO語言示例
// AddPolicy adds a policy rule to the storage.
func (a *Adapter) AddPolicy(sec string, ptype string, rule []string) error {
    return errors.New("not implemented")
}

// RemovePolicy removes a policy rule from the storage.
func (a *Adapter) RemovePolicy(sec string, ptype string, rule []string) error {
    return errors.New("not implemented")
}

// RemoveFilteredPolicy removes policy rules that match the filter from the storage.
func (a *Adapter) RemoveFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) error {
    return errors.New("not implemented")
}

casbin執(zhí)行器在調用這三個可選函數(shù)時將忽略未實現(xiàn)錯誤。

關于數(shù)據(jù)庫表結構的創(chuàng)建

作為約定,適配器應該能夠自動創(chuàng)建一個名為casbin的數(shù)據(jù)庫(如果它不存在),并將其用于策略存儲。請使用xorm適配器作為參考實現(xiàn):https://github.com/casbin/xorm-adapter

Watchers

? 我們支持使用像etcd這樣的分布式消息傳遞系統(tǒng)來保持多個casbin執(zhí)行器實例之間的一致性。因此,我們的用戶可以同時使用多個Casbin enforcers來處理大量的權限檢查請求。

與策略存儲 adapters類似,我們沒有把watcher的代碼放在主庫中。 任何對新消息系統(tǒng)的支持都應該作為atcher程序來實現(xiàn)。 完整的Casbin watchers列表如下所示。 歡迎任何第三方對 watcher 進行新的貢獻,如果有請告知我們,我將把它放在這個列表中:

Watcher Type Author Description
Etcd Watcher KV store Casbin Watcher for etcd
NATS Watcher Messaging system Soluto Watcher for NATS
ZooKeeper Watcher KV store Grepsr Watcher for Apache ZooKeeper
Redis Watcher KV store @billcobbler Watcher for Redis
GCP Pub/Sub Watcher Messaging system LivingPackets Watcher for Google Cloud Platform PUB/SUB

Role Managers

角色管理器用于管理Casbin中的RBAC角色層次結構(用戶角色映射)。 角色管理器可以從Casbin策略規(guī)則或外部源(如LDAP、Okta、Auth0、Azure AD等) 檢索角色數(shù)據(jù)。 我們支持角色管理器的不同實現(xiàn)。 為了保持代碼輕量級,我們沒有把角色管理器代碼放在主庫中(默認的角色管理器除外)。 下面提供了Casbin角色管理器的完整列表。 歡迎任何第三方對角色管理器進行新的貢獻,如果有請告知我們,我將把它放在這個列表中:

Role manager Author Description
Default Role Manager (built-in) Casbin 支持存儲在Casbin策略中的角色層次結構

對于開發(fā)人員:所有角色管理器都必須實現(xiàn)RoleManager接口。默認角色管理器可以用作引用實現(xiàn)。

中間件

WEB框架

Name Description
Laravel The PHP framework for web artisans, via plugin: laravel-casbin
Laravel An authorization library for the laravel framework, via plugin: Laravel Authorization
Yii PHP Framework A fast, secure, and efficient PHP framework, via plugin: yii-casbin
CakePHP Build fast, grow solid PHP Framework, via plugin: cake-casbin
CodeIgniter4 Associate users with roles and permissions in CodeIgniter4 Web Framework, via plugin: CodeIgniter Permission
ThinkPHP 5.1 The ThinkPHP 5.1 framework, via plugin: think-casbin
ThinkPHP 6.0 The ThinkPHP 6.0 framework, via plugin: think-authz
Symfony The Symfony PHP framework, via plugin: symfony-casbin

API

管理 API

提供對Casbin策略管理完全支持的基本API。

參考

全局變量 e是執(zhí)行者實例。

$e = new Enforcer('examples/rbac_model.conf', 'examples/rbac_policy.csv');

獲取當前策略中顯示的主題列表:

$allSubjects = $e->getAllSubjects();

獲取當前命名策略中顯示的主題列表:

$allNamedSubjects = $e->getAllNamedSubjects("p");

獲取當前策略中顯示的對象列表:

$allObjects = $e->getAllObjects();

獲取當前命名策略中顯示的對象列表:

$allNamedObjects = $e->getAllNamedObjects("p");

獲取當前策略中顯示的操作列表:

$allActions = $e->getAllActions();

獲取當前命名策略中顯示的操作列表:

$allNamedActions = $e->getAllNamedActions("p");

獲取當前策略中顯示的角色列表

$allRoles = $e->getAllRoles();

獲取當前命名策略中顯示的角色列表:

$allNamedRoles = $e->getAllNamedRoles('g');

獲取策略中的所有授權規(guī)則:

$policy = $e->getPolicy();

獲取策略中的所有授權規(guī)則,可以指定字段篩選器:

$filteredPolicy = $e->getFilteredPolicy(0, "alice");

獲取命名策略中的所有授權規(guī)則:

$namedPolicy = $e->getNamedPolicy("p");

獲取命名策略中的所有授權規(guī)則,可以指定字段過濾器:

$filteredNamedPolicy = $e->getFilteredNamedPolicy("p", 0, "bob");

獲取策略中的所有角色繼承規(guī)則:

$groupingPolicy = $e->getGroupingPolicy();

獲取策略中的所有角色繼承規(guī)則,可以指定字段篩選器:

$filteredGroupingPolicy = $e->getFilteredGroupingPolicy(0, "alice");

獲取策略中的所有角色繼承規(guī)則:

$namedGroupingPolicy = $e->getNamedGroupingPolicy("g");

獲取策略中的所有角色繼承規(guī)則

$namedGroupingPolicy = $e->getFilteredNamedGroupingPolicy("g", 0, "alice");

確定是否存在授權規(guī)則

$hasPolicy = $e->hasPolicy('data2_admin', 'data2', 'read');

?

確定是否存在命名授權規(guī)則

$hasNamedPolicy = $e->hasNamedPolicy("p", "data2_admin", "data2", "read");

向當前策略添加授權規(guī)則。 如果規(guī)則已經(jīng)存在,函數(shù)返回false,并且不會添加規(guī)則。 否則,函數(shù)通過添加新規(guī)則并返回true

$added = $e->addPolicy('eve', 'data3', 'read');

向當前命名策略添加授權規(guī)則。 如果規(guī)則已經(jīng)存在,函數(shù)返回false,并且不會添加規(guī)則。 否則,函數(shù)通過添加新規(guī)則并返回true:

$added = $e->addNamedPolicy("p", "eve", "data3", "read");

從當前策略中刪除授權規(guī)則

$removed = $e->removePolicy("alice", "data1", "read");

移除當前策略中的授權規(guī)則,可以指定字段篩選器。 RemovePolicy 從當前策略中刪除授權規(guī)則:

$removed = $e->removeFilteredPolicy(0, "alice", "data1", "read");

從當前命名策略中刪除授權規(guī)則:

$removed = $e->removeNamedPolicy("p", "alice", "data1", "read");

從當前命名策略中移除授權規(guī)則,可以指定字段篩選器:

$removed = $e->removeFilteredNamedPolicy("p", 0, "alice", "data1", "read");

確定是否存在角色繼承規(guī)則

$has = $e->hasGroupingPolicy("alice", "data2_admin");

確定是否存在命名角色繼承規(guī)則:

$has = $e->hasNamedGroupingPolicy("g", "alice", "data2_admin");

向當前策略添加角色繼承規(guī)則。 如果規(guī)則已經(jīng)存在,函數(shù)返回false,并且不會添加規(guī)則。 如果規(guī)則已經(jīng)存在,函數(shù)返回false,并且不會添加規(guī)則:

$added = $e->addGroupingPolicy("group1", "data2_admin");

將命名角色繼承規(guī)則添加到當前策略。 如果規(guī)則已經(jīng)存在,函數(shù)返回false,并且不會添加規(guī)則。 否則,函數(shù)通過添加新規(guī)則并返回true:

$added = $e->addNamedGroupingPolicy("g", "group1", "data2_admin");

從當前策略中刪除角色繼承規(guī)則:

$removed = $e->removeGroupingPolicy("alice", "data2_admin");

從當前策略中移除角色繼承規(guī)則,可以指定字段篩選器:

$removed = $e->removeFilteredGroupingPolicy(0, "alice");

從當前命名策略中移除角色繼承規(guī)則:

$removed = $e->removeNamedGroupingPolicy("g", "alice");

從當前命名策略中移除角色繼承規(guī)則,可以指定字段篩選器:

$removed = $e->removeFilteredNamedGroupingPolicy("g", 0, "alice");

添加自定義函數(shù):

func customFunction($key1, $key2) {
    if ($key1 == "/alice_data2/myid/using/res_id" && $key2 == "/alice_data/:resource") {
        return true;
    } elseif ($key1 == "/alice_data2/myid/using/res_id" && $key2 == "/alice_data2/:id/using/:resId") {
        return true;
    } else {
        return false;
    }
}

func customFunctionWrapper(...$args){
    $key1 := $args[0];
    $key2 := $args[1];

    return customFunction($key1, $key2);
}

$e->addFunction("keyMatchCustom", customFunctionWrapper);

RBAC API

一個更友好的RBAC API。 這個API是Management API的子集。 RBAC用戶可以使用這個API來簡化代碼。

參考

全局變量 e是實施者實例。

$e = new Enforcer('examples/rbac_model.conf', 'examples/rbac_policy.csv');

獲取用戶具有的角色:

$res = $e->getRolesForUser("alice");

獲取具有角色的用戶:

$res = $e->getUsersForRole("data1_admin");

確定用戶是否具有角色:

$res = $e->hasRoleForUser("alice", "data1_admin");

為用戶添加角色。 如果用戶已經(jīng)擁有該角色(aka不受影響),則返回false:

$e->addRoleForUser("alice", "data2_admin");

刪除用戶的角色。 如果用戶沒有該角色(aka不受影響),則返回false:

$e->deleteRoleForUser("alice", "data1_admin");

刪除用戶的所有角色。 如果用戶沒有任何角色(aka不受影響),則返回false:

$e->deleteRolesForUser("alice");

刪除一個用戶。 如果用戶不存在,則返回false(也就是說不受影響):

$e->deleteUser("alice");

刪除一個角色:

$e->deleteRole("data2_admin");

刪除權限。 如果權限不存在,則返回false(aka不受影響):

$e->deletePermission("read");

為用戶或角色添加權限。 如果用戶或角色已經(jīng)擁有該權限(aka不受影響),則返回false:

$e->addPermissionForUser("bob", "read");

刪除用戶或角色的權限。 如果用戶或角色沒有權限(aka不受影響),則返回false:

$e->deletePermissionForUser("bob", "read");

刪除用戶或角色的權限。 如果用戶或角色沒有任何權限(aka不受影響),則返回false:

$e->deletePermissionsForUser("bob");

獲取用戶或角色的權限:

$e->getPermissionsForUser("bob");

確定用戶是否具有權限:

$e->hasPermissionForUser("alice", []string{"read"});

獲取用戶具有的隱式角色。 與GetRolesForUser() 相比,該函數(shù)除了直接角色外還檢索間接角色:

例如:

g, alice, role:admin
g, role:admin, role:user

GetRolesForUser("alice") 只能獲取到: ["role:admin"].
But GetImplicitRolesForUser("alice") 卻能獲取到: ["role:admin", "role:user"].

$e->getImplicitRolesForUser("alice");

獲取用戶或角色的隱式權限。與getPermissionsForuser()相比,此函數(shù)檢索繼承角色的權限

p, admin, data1, read
p, alice, data2, read
g, alice, admin

GetPermissionsForUser("alice") 只能獲取到: [["alice", "data2", "read"]].
But GetImplicitPermissionsForUser("alice") 卻能獲取到: [["admin", "data1", "read"], ["alice", "data2", "read"]].

$e->getImplicitPermissionsForUser("alice");

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容