权限控制是每个管理系统的关键组成部分,而Casbin是一个强大且高效的开源访问控制库,支持各种访问控制模型,用于在全局范围内执行授权。
Casbin 目前支持 Golang、Java、C/C++、Node.js、Javascript、PHP、Laravel、Python、.NET (C#)、Delphi、Rust、Ruby、Swift (Objective-C)、Lua (OpenResty)、Dart (Flutter) 和 Elixir。
本文将通过简单易懂的流程演示 casbin 的工作原理及其不同的可用模型配置。
Casbin 的工作流程
在深入了解不同的模型配置之前,让我们先通过如下所示的简单概览图来了解 casbin 的工作流程。
上图将整个工作流程分为两个阶段,即配置策略和执行策略。
配置策略
此阶段定义了权限控制的方法,包括定义权限模型和权限策略。
STEP 1 Model(定义模型)
在这里,我们根据需求配置模型。我们使用CONF文件(.conf 文件扩展名)来抽象模型配置。此配置基于 PERM 元模型(Policy, Effect, Request, Matchers)分别表示策略,效果,请求,匹配器。
在上图中,演示了 Casbin 中最基本、最简单的ACL模型。
STEP 2 Policy(定义策略)
策略的基本逻辑是谁可以做什么
策略的基本语法是p = sub, obj, act, eft
这种语法可以理解为:谁(sub)可以/不能(eft:allow/deny)对某个资源(obj)执行什么(act)。
这里eft可以是 trueallow或 false deny。如果未指定,则默认值为 true allow。
上图中已指明了四条权限策略:
- John 可以阅读 RECORD1
- John 不能写入 RECORD1
- Harry 可以阅读 RECORD1
- Harry 可以修改 RECORD1
执行策略
此阶段基于模型和策略进行权限的判断。
STEP 3 Request(发起请求)
请求定义了用户尝试访问某个资源或对其执行所需操作时的动作。
图中所示有两条请求。
- John 阅读 RECORD1
- John 写入 RECORD1
STEP 4 Matcher(将请求匹配策略)
执行策略的第一步是根据请求查询匹配的策略。在上面的示例中,我们使用以下匹配表达式来确保请求中的主题、对象和操作与策略规则中的相应内容相匹配。我们可以把它理解为一个 SQL 查询的 WHERE 语句,r对象即是请求体,p 对象是策略列表中的记录。
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act
STEP 5 Policy Effect(最终判定)
匹配器可能查询到多条记录,这里定义了如何根据匹配到的策略决定最终结果。在上面的示例中,我们定义了以下策略,这个语句表示只要存在一个匹配的策略允许用户执行操作,最终结果即允许。
e = some(where (p.eft == allow))
另一种更严谨的验证方式同时要求确保没有策略禁止用户执行操作:
e = some(where (p.eft == allow)) && !some(where (p.eft == deny))
根据以上判定,casbin 的授权结果为
- John 可以阅读 RECORD1 ✔️
- John 不能写入 RECORD1 ❌
更多访问控制模型
前面演示了最简单的 ACL 访问控制模型,下面我们看看如何定义其他常见的访问控制模型,如 RBAC,ABAC 等。
基于角色的访问控制(RBAC)
ACL 提供了一对一的操作授权。这种方式在现实使用中往往会导致较大的工作量,RBAC将用户分配到不同的角色,并将权限分配给角色,而不是分配给单个用户。
假设共有三个用户:user1、user2 和 user3,以及两个角色:admin 和 user。在这种情况下,我们定义权限的对象是角色,而不是单个用户。
用户1和用户2拥有用户角色,\
用户3拥有管理员角色。用户只能读取记录1(user1,user2),\
管理员可以读写记录1(user3)。
Casbin 模型配置
[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
与之前相同,但我们g在这里添加了一个参数。
g = _, _定义了用户的角色。
matcher中也增加了一个表达式g(r.sub, p.sub),表示了g的字段,第一个是 r.sub表示发起请求的主体即用户,第二个是p.sub表示授权的主体即角色,下面我们就可以在 policy 列表中定义用户所属的角色信息了。
// user1 是管理员 admin,admin 可以写 record1 ,也就是 user1 可以写 record1
p, admin, record1, write
g, user1, admin
基于资源角色的RBAC
就像我们将用户分组为角色一样,我们也可以将资源分组并进行分配,而不是一次分配一个资源。
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
g2 = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub) && g2(r.obj, p.obj) && r.act == p.act
这里我们添加了g2一个参数。
g2 = _, _定义资源的角色。
// record1 和 record2 被分组到记录中,用户 1 可以写入记录,这意味着用户 1 可以同时写入 record1 和 record2。p、用户1、记录、写入
p, user1, record, write
g2, record1, record
g2, record2, record
基于域(租户)的RBAC
这是基于角色的访问控制 (RBAC) 的另一种版本,当系统中存在多个租户时,它至关重要。用户在不同的租户中扮演不同的角色。
[request_definition]
r = sub, dom, obj, act
[policy_definition]
p = sub, dom, obj, act
[role_definition]
g = _, _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub, r.dom) && r.dom == p.dom && r.obj == p.obj && r.act == p.act
//用户1是租户1的管理员,用户2是租户2的管理员。租户1的管理员(即用户1)可以读取和写入数据1。同样,租户2的管理员(即用户2)可以读取和写入数据2。这也意味着用户1没有读取/写入数据2的权限,反之亦然。
p, admin, tenant1, data1, read
p, admin, tenant1, data1, write
p, admin, tenant2, data2, read
p, admin, tenant2, data2, writeg, user1, admin, tenant1
g, user2, admin, tenant2
基于属性的访问控制(ABAC)
如果上述任何模型都无法解决您的用例,还有另一种模型可以提供更细粒度的搜索/匹配。评估是基于特定属性(例如用户属性)进行的。在 Casbin 中,这些属性可以是主体、客体或操作的属性。
例如,基于角色的访问控制(RBAC)系统会授予所有经理访问权限,而基于属性的访问控制(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
此模态配置仅检查发出请求的用户是否是所请求对象的拥有者。
假设有以下请求:
user1 请求访问 {Owner:"user1"}\
user1 请求访问 {Owner:"user2"}
映射到 r = sub,obj,act
sub= user1\
obj= {Owner:"user1"}
又有r.sub == r.obj.Owner
user1 == user1 // 第一个请求被允许\
user1 != user2 // 第二个请求无法匹配 matcher,所以被拒绝
官方文档和工具
同样,Casbin 支持定义各种丰富的模型以适应不同的业务场景。您可以通过官方文档《支持的模型》进一步了解其他访问控制模型。
也可以利用官方提供的Casbin 在线编辑器尝试编写和执行不同的模型和策略。

