PHP-Casbin
基础知识
概述
Casbin是一个强大的、高效的开源访问控制框架,其权限管理机制支持多种访问控制模型。
Casbin是什么?
Casbin可以做到:
- 支持自定义请求的格式,默认的请求格式为
{subject, object, action}
。 - 具有访问控制模型model和策略policy两个核心概念。
- 支持RBAC中的多层角色继承,不止主体可以有角色,资源也可以具有角色。
- 支持超级用户,如
root
或Administrator
,超级用户可以不受授权策略的约束访问任意资源。 - 支持多种内置的操作符,如
keyMatch
,方便对路径式的资源进行管理,如/foo/bar
可以映射到/foo*
Casbin不能做到:
- 身份认证 authentication(即验证用户的用户名、密码),casbin只负责访问控制。应该有其他专门的组件负责身份认证,然后由casbin进行访问控制,二者是相互配合的关系。
- 管理用户列表或角色列表。 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 API 和 RBAC 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
- ACL (Access Control List, 访问控制列表)
- 具有的超级用户ACL
- 没有用户的 ACL: 对于没有身份验证或用户登录的系统尤其有用。
- 没有资源的 ACL: 某些场景可能只针对资源的类型, 而不是单个资源, 诸如
write-article
,read-log
等权限。 它不控制对特定文章或日志的访问。 - RBAC (基于角色的访问控制)
- 支持资源角色的RBAC: 用户和资源可以同时具有角色 (或组)。
- 支持域/租户的RBAC: 用户可以为不同的域/租户设置不同的角色集。
- ABAC (基于属性的访问控制): 支持利用
resource.Owner
这种语法糖获取元素的属性。 - RESTful: 支持路径, 如 /res/*, /res/: id 和 HTTP 方法, 如 GET, POST, PUT, DELETE。
- 拒绝优先: 支持允许和拒绝授权, 拒绝优先于允许。
- 优先级: 策略规则按照先后次序确定优先级,类似于防火墙规则。
例子
访问控制模型 | 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 |
拒绝优先 | rbac_model_with_deny.conf | rbac_policy_with_deny.csv |
优先级 | 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)
函数中参数的含义。
# 请求定义
[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
, p2
的policy 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
即可。 当您需要表示角色(或者组)与用户和资源的绑定关系时,可以使用g
和 g2
这样的表现形式。 请参见 rbac_model 和 rbac_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
角色。
有几个注意事项:
- Casbin 只存储用户角色的映射关系。
- Cabin 没有验证用户是否是有效的用户,或者角色是一个有效的角色。 这应该通过认证来解决。
- RBAC 系统中的用户名称和角色名称不应相同。因为Casbin将用户名和角色识别为字符串, 所以当前语境下Casbin无法得出这个字面量到底指代用户
alice
还是角色alice
。 这时,使用明确的role_alice
,问题便可迎刃而解。 - 假设
A
具有角色B
,B
具有角色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
分别列出所有的 u
和 r
。
但在使用多层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 1
和book 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。
值得注意的是:
- 只有
g
种的第一参数 (aka 用户) 支持模式函数。 您正在使用第三个参数(domain),目前不支持。
角色管理器
角色管理器用于管理Casbin中的RBAC角色层次结构(用户角色映射)。 角色管理器可以从Casbin策略规则或外部源(如LDAP、Okta、Auth0、Azure AD等)检索角色数据。 我们支持角色管理器的不同实现。 为了保持代码轻量级,我们没有把角色管理器代码放在主库中(默认的角色管理器除外)。 下面提供了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 授权扩展名中的角色层次结构 |
对于开发人员:所有角色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
可以读取data1
, alice
在tenant1
域中具有admin
角色,但在tenant2
域中具有user
角色, 所以alice可以有读取data1
的权限。 同理,因为alice
不是tenant2
的admin
,所以她访问不了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,您需要做两件事:
- 在模型匹配器中指定属性。
- 将元素的结构或类实例作为Casbin的
Enforce()
的参数传入。
Note:
- 目前,仅请求元素,例如
r.such
、[r.obj
,]r.action
等等支持ABAC的元素。 您不能在策略元素上使用它,比如p.sub
,因为在Casbin的策略中没有定义结构或者类。 - 您可以在一个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语言示例
// 初始化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")
// 创建一个 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实现。请参照:。
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) | 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. |
这里有一些你需要知道的事情:
- 如果使用显式或隐式adapter调用
casbin.NewEnforcer()
,策略将自动加载。 - 可以调用
e.LoadPolicy()
来从存储中重新加载策略规则。 - 如果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-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()
// 从文件或数据库中重新加载策略
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()函数切换此选项。默认情况下,该选项处于启用状态(如果适配器支持它)。
需要注意的是:
-
Auto-Save
特性是可选的。 Adapter可以选择是否实现它。 -
Auto-Save
只在Casbin enforcer使用的adapter支持它时才有效。 - 查看上述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 进行新的贡献,如果有请告知我们,我将把它放在这个列表中:
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策略规则或外部源(如LDAP、Okta、Auth0、Azure AD等) 检索角色数据。 我们支持角色管理器的不同实现。 为了保持代码轻量级,我们没有把角色管理器代码放在主库中(默认的角色管理器除外)。 下面提供了Casbin角色管理器的完整列表。 欢迎任何第三方对角色管理器进行新的贡献,如果有请告知我们,我将把它放在这个列表中:
Role manager | Author | Description |
---|---|---|
Default Role Manager (built-in) | Casbin | 支持存储在Casbin策略中的角色层次结构 |
对于开发人员:所有角色管理器都必须实现RoleManager接口。默认角色管理器可以用作引用实现。
中间件
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
是执行者实例。
$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:userGetRolesForUser("alice") 只能获取到: ["role:admin"].
But GetImplicitRolesForUser("alice") 却能获取到: ["role:admin", "role:user"].
$e->getImplicitRolesForUser("alice");
获取用户或角色的隐式权限。与getPermissionsForuser()相比,此函数检索继承角色的权限
p, admin, data1, read
p, alice, data2, read
g, alice, adminGetPermissionsForUser("alice") 只能获取到: [["alice", "data2", "read"]].
But GetImplicitPermissionsForUser("alice") 却能获取到: [["admin", "data1", "read"], ["alice", "data2", "read"]].
$e->getImplicitPermissionsForUser("alice");