注册
登录

您现在的位置是:首页 > 学无止境

PHP-Casbin

木木彡82 2021-06-09 21:24:19 0人围观
Casbin是一个强大的、高效的开源访问控制框架,其权限管理机制支持多种访问控制模型。

转载自:https://www.jianshu.com/p/e2a6d53cb244

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实例时,使用数据库代替文件,相关说明,后面将会提到

进行访问控制

$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规则。

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')

但是你应确保数组的长度大于 1,否则的话将会导致 panic 。

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

Model

支持的Models

  1. ACL (Access Control List, 访问控制列表)

  2. 具有的超级用户ACL

  3. 没有用户的 ACL: 对于没有身份验证或用户登录的系统尤其有用。

  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. 拒绝优先: 支持允许和拒绝授权, 拒绝优先于允许。

  11. 优先级: 策略规则按照先后次序确定优先级,类似于防火墙规则。

例子

访问控制模型Model 文件Policy 文件
ACLbasic_model.confbasic_policy.csv
具有超级用户的ACLbasic_model_with_root.confbasic_policy.csv
没有用户的ACLbasic_model_without_users.confbasic_policy_without_users.csv
没有资源的ACLbasic_model_without_resources.confbasic_policy_without_resources.csv
RBACrbac_model.confrbac_policy.csv
支持资源角色的RBACrbac_model_with_resource_roles.confrbac_policy_with_resource_roles.csv
支持域/租户的RBACrbac_model_with_domains.confrbac_policy_with_domains.csv
ABACabac_model.conf
RESTfulkeymatch_model.confkeymatch_policy.csv
拒绝优先rbac_model_with_deny.confrbac_policy_with_deny.csv
优先级priority_model.confpriority_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) 函数中参数的含义。

# 请求定义
[request_definition]
r = sub, obj, act

sub, obj, act 表示经典三元组: 访问实体 (Subject),访问资源 (Object) 和访问方法 (Action)。 但是, 你可以自定义你自己的请求表单, 如果不需要指定特定资源,则可以这样定义 sub、act ,或者如果有两个访问实体, 则为 sub、sub2、obj、act

Policy定义

[policy_definition] 部分是对policy的定义,以下文的 model 配置为例:

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

这些是我们对policy规则的具体描述

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

policy部分的每一行称之为一个策略规则, 每条策略规则通常以形如p, p2policy type开头。 如果存在多个policy定义,那么我们会根据前文提到的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时,该如何对多个决策结果进行集成以实现统一决策。 以下示例展示了一个只有一条规则生效,其余都被拒绝的情况:

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

该Effect原语表示如果存在任意一个决策结果为allow的匹配规则,则最终决策结果为allow,即allow-override。 其中p.eft 表示策略规则的决策结果,可以为allow 或者deny,当不指定规则的决策结果时,取默认值allow 。 通常情况下,policy的p.eft默认为allow, 因此前面例子中都使用了这个默认值。

这是另一个policy effect的例子:

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

该Effect原语表示不存在任何决策结果为deny的匹配规则,则最终决策结果为allow ,即deny-override。 some 量词判断是否存在一条策略规则满足匹配器。 any 量词则判断是否所有的策略规则都满足匹配器 (此处未使用)。

policy effect还可以利用逻辑运算符进行连接:

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

该Effect原语表示当至少存在一个决策结果为allow的匹配规则,且不存在决策结果为deny的匹配规则时,则最终决策结果为allow。 这时allow授权和deny授权同时存在,但是deny优先。

Matchers

[matchers] 原语定义了策略规则如何与访问请求进行匹配的匹配器,其本质上是布尔表达式,可以理解为Request、Policy等原语定义了关于策略和请求的变量,然后将这些变量代入Matcher原语中求值,从而进行策略决策。

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

这是一个简单的例子,该Matcher原语表示,访问请求request中的subject、object、action三元组应与策略规则policy rule中的subject、object、action三元组分别对应相同。

Matcher原语支持+、 -、 *、 /等算数运算符,==,、!=、 >、 <等关系运算符以及&& (与)、|| (或)、 ! (非)等逻辑运算符。

: 虽然可以像其他原语一样的编写多个类似于 m1, m2 的matcher, 但是当前我们只支持一个有效的 matcher m。 通常情况下,您可以在一个matcher中使用上文提到的逻辑运算符来实现复杂的逻辑判断, 因而我们认为目前不需要支持多个matcher。 如果您对此有疑问,请告知我们(https://github.com/casbin/casbin/issues)。

matcher中的函数

matcher的强大与灵活之处在于您甚至可以在matcher中定义函数,这些函数可以是内置函数或自定义的函数。当前支持的内置函数如下:

函数释义示例
keyMatch(arg1, arg2)参数 arg1 是一个 URL 路径,例如 /alice_data/resource1,参数 arg2 可以是URL路径或者是一个 * 模式,例如 /alice_data/*。此函数返回 arg1是否与 arg2 匹配。keymatch_model.conf/keymatch_policy.csv
keyMatch2(arg1, arg2)参数 arg1 是一个 URL 路径,例如 /alice_data/resource1,参数 arg2 可以是 URL 路径或者是一个 : 模式,例如/alice_data/:resource。此函数返回 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

如何添加自定义函数(GO语言示例)

首先准备好一个有几个参数和一个布尔值返回值的函数:

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{} 类型包装此函数:

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

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

最后, 将该函数注册到 Casbin enforcer:

e.AddFunction("my_func", KeyMatchFunc)

现在, 您可以像下面这样使用 model 中的函数:

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

RBAC

角色定义

[role_definition] 是RBAC角色继承关系的定义。 Casbin 支持 RBAC 系统的多个实例, 例如, 用户可以具有角色及其继承关系, 资源也可以具有角色及其继承关系。 这两个 RBAC 系统不会互相干扰。

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

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

上述角色定义表明, g 是一个 RBAC系统, g2 是另一个 RBAC 系统。 _, _表示角色继承关系的前项和后项,即前项继承后项角色的权限。 一般来讲,如果您需要进行角色和用户的绑定,直接使用g 即可。 当您需要表示角色(或者组)与用户和资源的绑定关系时,可以使用gg2 这样的表现形式。 请参见 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 系统中的用户名称和角色名称不应相同。因为Casbin将用户名和角色识别为字符串, 所以当前语境下Casbin无法得出这个字面量到底指代用户 alice 还是角色 alice。 这时,使用明确的 role_alice ,问题便可迎刃而解。

  4. 假设A具有角色 BB 具有角色 C,并且 A 有角色 C。 这种传递性在当前版本会造成死循环。

角色层次

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}

如何区分用户和角色?

在RBAC中,Casbin不对用户和角色进行区分。 它们都被视为字符串。 如果你只使用单层的RBAC模型(角色不会成为另一个角色的成员)。 可以使用 e.GetAllSubjects() 获取所有用户,e.GetAllRoles() 获取所有角色。 它们会为规则 g, u, r 分别列出所有的 ur

但在使用多层RBAC模型时(带有角色继承),你的应用不会记录一个名字(字符串)是用户还是角色,或者用户和角色有相同的名字。 可以给角色加上像 role::admin 的前缀再传递到Casbin中。 由此可以通过查看前缀来区分用户和角色。

如何查询隐性角色或权限?

当用户通过RBAC层次结构继承角色或权限,而不是直接在策略规则中分配它们时,我们将这种类型的分配称为 implicit。 要查询这种隐式关系,需要使用以下两个api: GetImplicitRolesForUser()以及 GetImplicitPermissionsForUser 替代GetRolesForUser() 以及 GetPermissionsForUser. 有关详情,请参阅 this GitHub issue

在 RBAC 中使用模式匹配

有时,您希望将具有特定模式的某些subjects(或objects)自动授予某个角色。 RBAC中的模式匹配函数可以帮助做到这一点。 模式匹配函数与前一个函数共享相同的参数和返回值: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 策略规则将每一个book添加到book角色(或组)是非常单调乏味的。

但是使用模式匹配函数,您可以只用一行代码编写策略:

g, /book/:id, book_group

Casbin会自动将 /book/1/book/2匹配成模式/book/:id。 您只需要向强制程序注册该功能,如:

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

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

值得注意的是:

  1. 只有g种的第一参数 (aka 用户) 支持模式函数。 您正在使用第三个参数(domain),目前不支持。

角色管理器

角色管理器用于管理Casbin中的RBAC角色层次结构(用户角色映射)。 角色管理器可以从Casbin策略规则或外部源(如LDAP、Okta、Auth0、Azure AD等)检索角色数据。 我们支持角色管理器的不同实现。 为了保持代码轻量级,我们没有把角色管理器代码放在主库中(默认的角色管理器除外)。 下面提供了Casbin角色管理器的完整列表。 欢迎任何第三方对角色manager进行新的贡献,如果有请告知我们,我们将把它放在这个列表中:

角色管理器作者描述
Default Role Manager (built-in)Casbin支持存储在Casbin策略中的角色层次结构
Session Role ManagerEDOMO Systems支持存储在Casbin策略中的角色层次结构,以及基于时间范围的会话
Okta Role ManagerCasbin支持存储在Okta中的角色层次结构
Auth0 Role ManagerCasbin支持存储在 Auth0's Authorization Extension 授权扩展名中的角色层次结构

对于开发人员:所有角色manager必须实现 RoleManager接口。 Session Role Manager可以用作参考实现。

RBAC with Domains

域租户的角色定义

在Casbin中的RBAC角色可以是全局或是基于特定于域的。 特定域的角色意味着当用户处于不同的域/租户群体时,用户所表现的角色也不尽相同。 这对于像云服务这样的大型系统非常有用,因为用户通常分属于不同的租户群体。

域/租户的角色定义应该类似于:

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

第三个 _ 表示域/租户的名称, 此部分不应更改。

然后,策略可以是:

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

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

该实例表示tenant1的域内角色admin 可以读取data1alicetenant1域中具有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是 基于属性的访问控制,可以使用主体、客体或动作的属性,而不是字符串本身来控制访问。 您之前可能就已经听过 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() 函数中传递的 r.obj 函数是结构或类实例,而不是字符串。 Casbin将使用映像来检索 obj结构或类中的成员变量。

这里是 r.obj construction 或 class 的定义:

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

如何使用ABAC?

简单地说,要使用ABAC,您需要做两件事:

  1. 在模型匹配器中指定属性。

  2. 将元素的结构或类实例作为Casbin的Enforce() 的参数传入。

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 不是动态组件,不应该在运行时进行修改,所以我们没有实现一个 API 来将 model 保存到存储中。

但是,好消息是,我们提供了三种等效的方法来静态或动态地加载模型:

从 .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

型可以从代码中动态初始化,不需要使用 .CONF。下面是RBAC模型的一个例子:

// GO语言示例// 初始化modelm := 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")// 创建一个 enforcer。e := casbin.NewEnforcer(m, a)

从字符串加载的 model

或者您可以从多行字符串加载整个模型文本。这种方法的优点是您不需要维护模型文件。

// 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实现。请参照:</docs/en/adapters>。

Policy Subset Loading

一些adapter支持过滤策略管理。 这意味着Casbin加载的策略是基于给定过滤器的存储策略的子集。 当解析整个策略成为性能瓶颈时,这将会允许在大型多租户环境中有效地执行策略。

要使用支持的adapter处理过滤后的策略,只需调用 LoadFilteredPolicy 方法。 过滤器参数的有效格式取决于所用的适配器。 为了防止意外数据丢失,当策略已经加载, 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的中间件) 实现。 Casbin用户可以使用adapte从存储中加载策略规则 (aka LoadPolicy()) 或者将策略规则保存到其中 (aka SavePolicy())。 为了保持代码轻量级,我们没有把adapte代码放在主库中。

目前支持的adapter列表

Casbin角色管理器的完整列表如下所示。 欢迎任何第三方对adapter进行新的贡献,如果有请通知我们,我们将把它放在这个列表中:)

适配器类型作者自动保存描述
File Adapter (built-in)FileCasbinFor .CSV (Comma-Separated Values) files
Database AdapterORMCasbinMySQL, PostgreSQL, SQLite, Microsoft SQL Server are supported by techone/database
Zend Db AdapterORMCasbinMySQL, PostgreSQL, SQLite, Oracle, IBM DB2, Microsoft SQL Server, Other PDO Driver are supported by zend-db
Doctrine DBAL Adapter(Recommend)ORMCasbinPowerful PHP database abstraction layer (DBAL) with many features for database schema introspection and management.
Medoo AdapterORMCasbinMedoo 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() 来从存储中重新加载策略规则。

  3. 如果adapter不支持Auto-Save特性,则在添加或删除策略时不能将策略规则自动保存回存储器。 您可以手动调用SavePolicy() 来保存所有策略规则。

例子

这里我门提供了几个例子:

文件适配器 (内置)

下面展示了如何对内置的文件适配器进行初始化:

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-adapteruse 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()// 从文件或数据库中重新加载策略e.LoadPolicy()// Save the current policy (usually after changed with Casbin API) back to file/database.// 将当前策略保存到文件或数据库(通常在使用Casbin API之后)e.SavePolicy()

自动保存

有一个称为适配器自动保存的功能。当一个适配器支持自动保存功能时,就意味着它能够支持从存储器中添加一条策略或删除一条策略。这与savePolicy()不同,后者将删除存储中的所有策略规则,并将所有策略规则从casbin Enforcer保存到存储中。因此,当策略规则的数量很大时,它可能会遇到性能问题。

当适配器支持自动保存时,可以通过enforcer.enableautosave()函数切换此选项。默认情况下,该选项处于启用状态(如果适配器支持它)。

需要注意的是:

  1. Auto-Save 特性是可选的。 Adapter可以选择是否实现它。

  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

所有适配器需要至少实现两个适配器接口中的两个方法:LoadPolicy(model model.Model) error and SavePolicy(model model.Model) error

其他三个功能是可选的。如果适配器支持自动保存功能,则应该实现它们。

方法类型描述
LoadPolicy()强制的从存储中加载所有策略规则
SavePolicy()强制的将所有策略规则保存到存储中
AddPolicy()可选择的向存储中添加策略规则
RemovePolicy()可选择的从存储中删除策略规则
RemoveFilteredPolicy()可选择的从存储中删除匹配筛选器的策略规则

注意:如果适配器不支持自动保存功能,它应该为三个可选功能提供一个空实现。如果您不提供它,编译器将会报错。下面是一个例子:

// 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执行器在调用这三个可选函数时将忽略未实现错误。

关于数据库表结构的创建

作为约定,适配器应该能够自动创建一个名为casbin的数据库(如果它不存在),并将其用于策略存储。请使用xorm适配器作为参考实现:https://github.com/casbin/xorm-adapter

Watchers

我们支持使用像etcd这样的分布式消息传递系统来保持多个casbin执行器实例之间的一致性。因此,我们的用户可以同时使用多个Casbin enforcers来处理大量的权限检查请求。

与策略存储 adapters类似,我们没有把watcher的代码放在主库中。 任何对新消息系统的支持都应该作为atcher程序来实现。 完整的Casbin watchers列表如下所示。 欢迎任何第三方对 watcher 进行新的贡献,如果有请告知我们,我将把它放在这个列表中:

WatcherTypeAuthorDescription
Etcd WatcherKV storeCasbinWatcher for etcd
NATS WatcherMessaging systemSolutoWatcher for NATS
ZooKeeper WatcherKV storeGrepsrWatcher for Apache ZooKeeper
Redis WatcherKV store@billcobblerWatcher for Redis
GCP Pub/Sub WatcherMessaging systemLivingPacketsWatcher for Google Cloud Platform PUB/SUB

Role Managers

角色管理器用于管理Casbin中的RBAC角色层次结构(用户角色映射)。 角色管理器可以从Casbin策略规则或外部源(如LDAP、Okta、Auth0、Azure AD等) 检索角色数据。 我们支持角色管理器的不同实现。 为了保持代码轻量级,我们没有把角色管理器代码放在主库中(默认的角色管理器除外)。 下面提供了Casbin角色管理器的完整列表。 欢迎任何第三方对角色管理器进行新的贡献,如果有请告知我们,我将把它放在这个列表中:

Role managerAuthorDescription
Default Role Manager (built-in)Casbin支持存储在Casbin策略中的角色层次结构

对于开发人员:所有角色管理器都必须实现RoleManager接口。默认角色管理器可以用作引用实现。

中间件

WEB框架

NameDescription
LaravelThe PHP framework for web artisans, via plugin: laravel-casbin
LaravelAn authorization library for the laravel framework, via plugin: Laravel Authorization
Yii PHP FrameworkA fast, secure, and efficient PHP framework, via plugin: yii-casbin
CakePHPBuild fast, grow solid PHP Framework, via plugin: cake-casbin
CodeIgniter4Associate users with roles and permissions in CodeIgniter4 Web Framework, via plugin: CodeIgniter Permission
ThinkPHP 5.1The ThinkPHP 5.1 framework, via plugin: think-casbin
ThinkPHP 6.0The ThinkPHP 6.0 framework, via plugin: think-authz
SymfonyThe Symfony PHP framework, via plugin: symfony-casbin

API

管理 API

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

参考

全局变量 e是执行者实例。

$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');

获取策略中的所有授权规则:

$policy = $e->getPolicy();

获取策略中的所有授权规则,可以指定字段筛选器:

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

获取命名策略中的所有授权规则:

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

获取命名策略中的所有授权规则,可以指定字段过滤器:

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

获取策略中的所有角色继承规则:

$groupingPolicy = $e->getGroupingPolicy();

获取策略中的所有角色继承规则,可以指定字段筛选器:

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

获取策略中的所有角色继承规则:

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

获取策略中的所有角色继承规则

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

确定是否存在授权规则

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


确定是否存在命名授权规则

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

向当前策略添加授权规则。 如果规则已经存在,函数返回false,并且不会添加规则。 否则,函数通过添加新规则并返回true

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

向当前命名策略添加授权规则。 如果规则已经存在,函数返回false,并且不会添加规则。 否则,函数通过添加新规则并返回true:

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

从当前策略中删除授权规则

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

移除当前策略中的授权规则,可以指定字段筛选器。 RemovePolicy 从当前策略中删除授权规则:

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

从当前命名策略中删除授权规则:

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

从当前命名策略中移除授权规则,可以指定字段筛选器:

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

确定是否存在角色继承规则

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

确定是否存在命名角色继承规则:

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

向当前策略添加角色继承规则。 如果规则已经存在,函数返回false,并且不会添加规则。 如果规则已经存在,函数返回false,并且不会添加规则:

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

将命名角色继承规则添加到当前策略。 如果规则已经存在,函数返回false,并且不会添加规则。 否则,函数通过添加新规则并返回true:

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

从当前策略中删除角色继承规则:

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

从当前策略中移除角色继承规则,可以指定字段筛选器:

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

从当前命名策略中移除角色继承规则:

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

从当前命名策略中移除角色继承规则,可以指定字段筛选器:

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

添加自定义函数:

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");

为用户添加角色。 如果用户已经拥有该角色(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");

为用户或角色添加权限。 如果用户或角色已经拥有该权限(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() 相比,该函数除了直接角色外还检索间接角色:

例如:

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

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

$e->getImplicitRolesForUser("alice");

获取用户或角色的隐式权限。与getPermissionsForuser()相比,此函数检索继承角色的权限

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");



文章评论

  • 登录后评论

点击排行