Casdoor的权限检查

Casdoor 的权限检查机制是基于 Casbin 的,参考文档:

简要介绍

所有在同一个组织的用户可以访问该组织下的所有应用,但是有时候需要做一些限制,这里就要用到 权限(permission) 了。

那么 权限(permission) 的作用就是用于控制 用户 能否对 应用 进行某些 操作

最简单的方式就是在 权限(permission) 配置页面新建一个权限,如下图所示:

image.png

作为三个要素,用户,应用和操作,在这里是需要配置的:

  • 包含用户
  • 资源
  • 动作

意义也比较简单,就是哪些 用户 可以对哪些 应用 进行哪些 操作

包含角色 也容易理解,一个角色对应多个人,所以这里就是用来控制一组人的。

那上面的模型是什么意思呢,Casdoor 是如何根据这个配置进行权限校验的呢?

ACL 权限控制模型

ACL 权限控制模型应该很熟悉了吧,Linux 上就在使用,这里就不再赘述,参考:

Casbin 的权限控制

Casbin 是一个开源的权限控制库,它支持了多种权限控制模型

Casbin 实施权限规则比较简单,管理员需要列出 主体(subject)对象(object) 和期望允许的 操作(action) 即可:

  • subject,主体,比如用户
  • object,对象,需要被权限控制的对象
  • action,动作,read,write,delete或者其他定义的动作(操作)

管理员需要定义 模型(model) 文件来确定校验条件,Casbin 提供了一个 执行器(Enforcer) 来根据规则定义和模型文件来校验请求。

也就是说 Casbin 权限校验需要三个部分:模型(Model), 校验规则(Policy)和执行器(Enforcer)。

执行器是 Casbin 自带的,可以不必特别关注,只需要知道这件事情就行。下来着重研究模型和校验规则。

哦,对了进行权限校验还需要一个 请求(request)

模型定义

Casbin 里的权限控制模型被抽象成了一个 CONF 文件,基于 PERM metamodel (Policy, Effect, Request, Matchers),这个 PERM metamodel 基于 4 个部分 Policy, Effect, Request, Matchers,其描述了 资源 和 用户 之间的关系。

Casbin 里最简单的一个模型,也是默认模型:

1
2
3
4
5
6
7
8
9
10
11
[request_definition]
r = sub, obj, act

[policy_definition]
p = permission, 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
Request

r = sub, obj, act

定义了请求参数,也就是说请求需要三个部分,subject,object 和 action,通俗点讲就是 访问的用户,访问的对象,访问的方法。

这里其实定义了我们应该提供给权限控制匹配函数的参数名和顺序。

Policy

p = permission, sub, obj, act

定义了模型的访问策略,其实是定义了在策略规则文档中字段名和顺序

这里还有一种定义:p={sub, obj, act, eft}

eft是可以被省略的,eft代表的是策略的结果,比如就是 allow 或者是 deny,如果没有定义的话,读取策略文件的时候就会被忽略,默认会返回 allow

Matcher

m = r.sub == p.sub && r.obj == p.obj && r.act == p.act

请求和策略的匹配规则,这里 r 就是请求(request),p 就是策略(policy)

Effect

e = some(where (p.eft == allow))

就是对策略的结果再次进行条件判断,这里的意思就是只要有一条规则返回的是 allow,那么整个就是 allow

还有这样的定义:e = some(where (p.eft == allow)) && !some(where (p.eft == deny))

就是说有一条是 allow 而且 没有 deny 的情况,整个结果是 allow

这里主要是针对多条匹配规则都匹配上的情况做处理的

在 Casdoor 定义权限控制

定义模型

我们已经了解了模型的定义规则,那么就开始在 Casdoor 上面实际操作一番吧。

模型定义用来针对要控制的 object,访问的 subject 能允许怎样的 action

首先在页面顶部导航栏点击 模型(Model),点击新增:

image.png

模型文本这里就填写上面介绍过的模型定义字符串,点击保存&退出

定义权限

在页面顶部导航栏点击 权限(permission),点击新增:

image.png

permission 其实就是具体的控制权限设置,可以针对多个资源进行控制

其实这里的配置界面只是简化了规则的配置,这个页面的权限其实会转换成类似如下的数据:

1
2
p	alice	data1	read
p bob data2 write

代码上

image.png

permission 里定义的规则会先转换成 CasbinRule 对象,每一个对象就包含了 Policy 定义的每个字段,之后就会转成如下的字符串:

p, built-in/permission-built-in, built-in/app-built-in/admin, app-built-in, read

执行校验

模型和权限定义好之后,对应被控制的用户访问被控制的应用时,Casdoor 就会进行加载对应模型和权限进行校验,执行这一工作的叫做 Enforcer

关于 Enforcer 的文档:New a Casbin enforcer

代码分析

流程已经分析差不多了,让我们看看代码上是怎么处理的

Casdoor 是在这个函数做权限校验的。用户登陆时候就会调用,当然在任何你想进行权限校验的地方,都可以进行调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
func CheckAccessPermission(userId string, application *Application) (bool, error) {
permissions := GetPermissions(application.Organization)
allowed := true
var err error
for _, permission := range permissions {
if !permission.IsEnabled || len(permission.Users) == 0 {
continue
}

isHit := false
for _, resource := range permission.Resources {
if application.Name == resource {
isHit = true
break
}
}

if isHit {
enforcer := getEnforcer(permission) // v2.Enforcer
allowed, err = enforcer.Enforce(userId, application.Name, "read")
break
}
}
return allowed, err
}

逻辑就是通过 organization 来获取所有的 permission 配置,接下来遍历 permission 中定义的 resource,如果跟当前的应用匹配,那么用这条权限去检查用户是否有对应的权限。

匹配成功之后,调用 getEnforcer 来获取一个执行权限校验的 enforcer。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
func getEnforcer(permission *Permission) *casbin.Enforcer {
tableNamePrefix := conf.GetConfigString("tableNamePrefix")
adapter, err := xormadapter.NewAdapterWithTableName(conf.GetConfigString("driverName"), conf.GetBeegoConfDataSourceName()+conf.GetConfigString("dbName"), "permission_rule", tableNamePrefix, true)
if err != nil {
panic(err)
}

modelText := `
[request_definition]
r = sub, obj, act

[policy_definition]
p = permission, 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`

permissionModel := getModel(permission.Owner, permission.Model)
if permissionModel != nil {
modelText = permissionModel.ModelText
}
m, err := model.NewModelFromString(modelText)
if err != nil {
panic(err)
}

enforcer, err := casbin.NewEnforcer(m, adapter)
if err != nil {
panic(err)
}

err = enforcer.LoadFilteredPolicy(xormadapter.Filter{V0: []string{permission.GetId()}})
if err != nil {
panic(err)
}

return enforcer
}

getEnforcer 做的工作就是根据 权限(permission) 数据去看有没有定义好的model,有的话使用定义好的模型文本来加载 enforcer,否则使用默认的模型文本:

1
2
3
4
5
6
7
8
9
10
11
[request_definition]
r = sub, obj, act

[policy_definition]
p = permission, 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

加载模型文本之后,enforcer 就会加载对应 权限(permission) 所对应定义的规则,对应 LoadFilteredPolicy 函数。

这些数据加载完成之后,enforcer 会根据传入的 subject,entity,action,即 用户,应用,操作,来检查权限是否允许

allowed, err = enforcer.Enforce(userId, application.Name, "read")

总结

总结一下,主要有以下核心:

  1. 理解 Casdoor 的权限是做什么的
  2. 理解 模型(Model) 以及 模型文本 是如何定义的
  3. 理解 模型,权限是如何配置的,以及是如何对用户,应用进行控制的
  4. 理解代码上是如何调用的,可以扩展到其他需要进行权限校验的地方

注意

在代码上新建或者编辑权限(permission) 之后,记得要重新生成一下规则(policy),因为不论在界面上是怎样的表现形式,最后都是enforcer通过model textpolicy来检查request

代码如下:

1
2
3
4
5
6
7
8
9
10
// 更新permission之后需要调用
if affected != 0 {
removePolicies(oldPermission)
addPolicies(permission)
}

// 新建permission之后需要调用
if affected != 0 {
addPolicies(permission)
}
作者

skyrover

发布于

2022-09-30

更新于

2022-10-09

许可协议

评论