与你相识

博主介绍:
– 本人是普通大学生一枚,每天钻研计算机技能,CSDN主要分享一些技术内容,因我常常去寻找资料,不经常能找到合适的,精品的,全面的内容,导致我花费了大量的时间,所以会将摸索的内容全面细致记录下来。另外,我更多关于管理,生活的思考会在简书中发布,如果你想了解我对生活有哪些反思,探索,以及对管理或为人处世经验的总结,我也欢迎你来找我。
– 目前的学习专注于Go语言,辅学算法,前端领域。也会分享一些校内课程的学习,例如数据结构,计算机组成原理等等,如果你喜欢我的风格,请关注我,我们一起成长。
Table of Contents
casbin
权限管理几乎在每个系统中都是必备的模块。如果项目开发每次都要实现一次权限管理,会浪费很多的时间,而casbin就来做这个事情,支持常用的多种访问控制模型,如ACL/RBAC/ABAC
等。可以实现灵活的访问权限控制。
比如说什么角色,什么用户,可以通过某个api,就可以使用casbin来进行限制。
官方文档
由于官方文档到中间靠后的部分有点难以理解,所以就转向其它教程了。
基础知识
安装及简单实用
go get github.com/casbin/casbin
另外创建一个Casbin决策器需要有一个模型文件和策略文件作为参数:
import "github.com/casbin/casbin"
e := casbin.NewEnforcer("path/to/model.conf", "path/to/policy.csv")
然后可以在访问发生之前,给代码添加强制挂钩:
sub := "alice" // 想要访问资源的用户。
obj := "data1" //要访问的资源。
act := "read" // 用户对资源执行的操作。
if e.Enforce(sub, obj, act) == true {
// 允许alice读取data1
} else {
// 拒绝请求,显示错误
}
工作原理
casbin的访问控制模型被抽象为基于PERM(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
// 对于过长的单元配置,也可以通过在结尾处添加“/”进行断行
m = r.sub == p.sub && r.obj == p.obj \
&& r.act == p.act
policy
如下:
// 这表示alice可以读取data1
p, alice, data1, read
// 这表示bob可以编写data2
p, bob, data2, write
Model
支持的Model
- 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语法
- Model CONF 至少应包含四个部分:
[request_definition], [policy_definition], [policy_effect], [matchers]
。 - 如果 model 使用 RBAC, 还需要添加
[role_definition]
部分。
Request定义
[request_definition]
部分用于request的定义,它明确了e.Enforce(...)
函数中参数的含义。
[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
这些是我们对policy规则的具体描述
p, alice, data1, read
policy部分的每一行称之为一个策略规则。 上面的policy的绑定关系将会在matcher中使用, 罗列如下:
(alice, data1, read) -> (p.sub, p.obj, p.act)
Policy effect定义
[policy_effect]
部分是对policy生效范围的定义, 原语定义了当多个policy rule同时匹配访问请求request时,该如何对多个决策结果进行集成以实现统一决策。 以下示例展示了一个只有一条规则生效,其余都被拒绝的情况:
[policy_effect]
e = some(where (p.eft == allow))
该Effect原语表示如果存在任意一个决策结果为allow
的匹配规则,则最终决策结果为allow
。 其中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原语支持+、 -、 *、 /等算数运算符,==,、!=、 >、 <等关系运算符以及&& (与)、|| (或)、 ! (非)等逻辑运算符。
内置函数
函数 | 释义 | 示例 |
---|---|---|
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 |
也可以添加自定义函数,具体参考:添加自定义函数
RBAC
看不懂
ABAC
看不懂
每日一库——casbin
ACL模型
下面可以看到我们使用ACL模型进行的权限控制
我们依然使用 Go Module 编写代码,先初始化:
$ mkdir casbin && cd casbin
$ go mod init github.com/darjun/go-daily-lib/casbin
然后安装casbin
,目前是v2
版本:
$ go get github.com/casbin/casbin/v2
权限实际上就是控制谁能对什么资源进行什么操作。casbin
将访问控制模型抽象到一个基于 PERM(Policy,Effect,Request,Matchers) 元模型的配置文件(模型文件)中。因此切换或更新授权机制只需要简单地修改配置文件。
policy
是策略或者说是规则的定义。它定义了具体的规则。
request
是对访问请求的抽象,它与e.Enforce()
函数的参数是一一对应的
matcher
匹配器会将请求与定义的每个policy
一一匹配,生成多个匹配结果。
effect
根据对请求运用匹配器得出的所有结果进行汇总,来决定该请求是允许还是拒绝。
编写模型文件:
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act
[policy_effect]
e = some(where (p.eft == allow))
上面模型文件规定了权限由sub,obj,act
三要素组成,只有在策略列表中有和它完全相同的策略时,该请求才能通过。匹配器的结果可以通过p.eft
获取,some(where (p.eft == allow))
表示只要有一条策略允许即可。
然后我们策略文件(即谁能对什么资源进行什么操作):
p, dajun, data1, read
p, lizi, data2, write
上面policy.csv
文件的两行内容表示dajun
对数据data1
有read
权限,lizi
对数据data2
有write
权限。
接下来就是使用的代码:
package main
import (
"fmt"
"log"
"github.com/casbin/casbin/v2"
)
func check(e *casbin.Enforcer, sub, obj, act string) {
ok, _ := e.Enforce(sub, obj, act)
if ok {
fmt.Printf("%s CAN %s %s\n", sub, act, obj)
} else {
fmt.Printf("%s CANNOT %s %s\n", sub, act, obj)
}
}
func main() {
e, err := casbin.NewEnforcer("./model.conf", "./policy.csv")
if err != nil {
log.Fatalf("NewEnforecer failed:%v\n", err)
}
check(e, "dajun", "data1", "read")
check(e, "lizi", "data2", "write")
check(e, "dajun", "data1", "write")
check(e, "dajun", "data2", "read")
}
代码其实不复杂。首先创建一个casbin.Enforcer
对象,加载模型文件model.conf
和策略文件policy.csv
,调用其Enforce
方法来检查权限。运行程序:
$ go run main.go
dajun CAN read data1
lizi CAN write data2
dajun CANNOT write data1
dajun CANNOT read data2
请求必须完全匹配某条策略才能通过。("dajun", "data1", "read")
匹配p, dajun, data1, read
,("lizi", "data2", "write")
匹配p, lizi, data2, write
,所以前两个检查通过。第 3 个因为"dajun"
没有对data1
的write
权限,第 4 个因为dajun
对data2
没有read
权限,所以检查都不能通过。输出结果符合预期。
sub/obj/act
依次对应传给Enforce
方法的三个参数。实际上这里的sub/obj/act
和read/write/data1/data2
是我自己随便取的,你完全可以使用其它的名字,只要能前后一致即可。
上面例子中实现的就是ACL
(access-control-list,访问控制列表)。ACL
显示定义了每个主体对每个资源的权限情况,未定义的就没有权限。我们还可以加上超级管理员,超级管理员可以进行任何操作。假设超级管理员为root
,我们只需要修改匹配器:
[matchers]
e = r.sub == p.sub && r.obj == p.obj && r.act == p.act || r.sub == "root"
只要访问主体是root
一律放行。
验证:
func main() {
e, err := casbin.NewEnforcer("./model.conf", "./policy.csv")
if err != nil {
log.Fatalf("NewEnforecer failed:%v\n", err)
}
check(e, "root", "data1", "read")
check(e, "root", "data2", "write")
check(e, "root", "data1", "execute")
check(e, "root", "data3", "rwx")
}
因为sub = "root"
时,匹配器一定能通过,运行结果:
$ go run main.go
root CAN read data1
root CAN write data2
root CAN execute data1
root CAN rwx data3
RBAC模型
ACL
在用户和资源比较少的情况下没有什么问题,但是如果用户很多的话,就会显得非常的繁琐,试想一下每次新增一个用户,都要把他需要的权限重新设置一遍是多么地痛苦。
所以RBAC
就通过引入角色(role)
来解决这个问题,每个用户都属于一个角色,每个角色都有其特定的权限,这样新增用户的时候,只需要给他指派一个角色,他就可以拥有该角色对应的权限信息。
使用RBAC
模型需要在ACL
MODEL的基础上添加role_definition
模块:
g = _,_
定义了用户——角色,角色——角色的映射关系,前者是后者的成员,拥有后者的权限。然后在匹配器中,我们不需要判断r.sub
与p.sub
是否相当,只需要使用g(r.sub, p.sub)
来判断请求主体r.sub
是否属于p.sub
这个角色即可。
[role_definition]
g = _, _
[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
最后再修改策略文件,下面的文件就规定了dajun
属于admin
管理员,lizi
属于developer
开发者,使用g
来定义这层关系。
另外具体的角色,如admin
对数据data有rede和write权限也通过p
来定义。
p, admin, data, read
p, admin, data, write
p, developer, data, read
g, dajun, admin
g, lizi, developer
我们编写主程序
package main
import (
"fmt"
"log"
"github.com/casbin/casbin/v2"
)
func check(e *casbin.Enforcer, sub, obj, act string) {
ok, _ := e.Enforce(sub, obj, act)
if ok {
fmt.Printf("%s CAN %s %s\n", sub, act, obj)
} else {
fmt.Printf("%s CANNOT %s %s\n", sub, act, obj)
}
}
func main() {
e, err := casbin.NewEnforcer("./model.conf", "./policy.csv")
if err != nil {
log.Fatalf("NewEnforecer failed:%v\n", err)
}
check(e, "dajun", "data", "read")
check(e, "dajun", "data", "write")
check(e, "lizi", "data", "read")
check(e, "lizi", "data", "write")
}
结果:
dajun CAN read data
dajun CAN write data
lizi CAN read data
lizi CANNOT write data
多个RBAC
可以为用户和资源都赋予角色的概念
[role_definition]
g=_,_
g2=_,_
[matchers]
m = g(r.sub, p.sub) && g2(r.obj, p.obj) && r.act == p.act
上面的模型文件定义了两个RBAC
系统g
和g2
,我们在匹配器中使用g(r.sub, p.sub)
判断请求主体属于特定组,g2(r.obj, p.obj)
判断请求资源属于特定组,且操作一致即可放行。
p, admin, prod, read
p, admin, prod, write
p, admin, dev, read
p, admin, dev, write
p, developer, dev, read
p, developer, dev, write
p, developer, prod, read
g, dajun, admin
g, lizi, developer
g2, prod.data, prod
g2, dev.data, dev
先看角色关系,即最后 4 行,dajun
属于admin
角色,lizi
属于developer
角色,prod.data
属于生产资源prod
角色,dev.data
属于开发资源dev
角色。admin
角色拥有对prod
和dev
类资源的读写权限,developer
只能拥有对dev
的读写权限和prod
的读权限。
check(e, "dajun", "prod.data", "read")
check(e, "dajun", "prod.data", "write")
check(e, "lizi", "dev.data", "read")
check(e, "lizi", "dev.data", "write")
check(e, "lizi", "prod.data", "write")
dajun CAN read prod.data
dajun CAN write prod.data
lizi CAN read dev.data
lizi CAN write dev.data
lizi CANNOT write prod.data
多层角色
可以为角色定义所属角色,这种权限关系可以进行传递。
例如dajun
属于高级开发者senior
,seinor
属于开发者,那么dajun
也是开发者,拥有开发者的所有权限,那么我们就可以定义开发者共有的权限,然后额外为高级开发者senior
定义一些特殊的权限。
模型文件不需要修改,策略文件改动如下:
# 定义了senior对数据data有write权限
p, senior, data, write
# 定义了developer对数据data有read权限
p, developer, data, read
# 为dajun赋予senior权限
g, dajun, senior
# 为senior赋予developer权限,senior权限本身对data有write权限,又被加了一个developer权限,就可写可读了。
g, senior, developer
# 为lizi赋予developer权限
g, lizi, developer
RBAC domain
角色可以是全局的,也可以是特定域(domain)的或租户(tenant),可以理解为组
。
例如dajun
在组tenant1
中是管理员,拥有很高的权限,但是在tenant2
中可能只是一个底层人员。
使用RBAC domain
需要对模型文件做以下修改:
[request_definition]
r = sub, dom, obj, act
[policy_definition]
p = sub, dom, obj, act
[role_definition]
g = _,_,_
[matchers]
m = g(r.sub, p.sub, r.dom) && r.dom == p.dom && r.obj == p.obj && r.act == p.obj
g=_,_,_
表示前者在后者中拥有中间定义的角色,在匹配器中使用g
要带上dom
。
# admin在tenant1中对data1有read权限
p, admin, tenant1, data1, read
# admin在tenant2中对data2有read权限
p, admin, tenant2, data2, read
# dajun在tenant1中的角色是admin
g, dajun, admin, tenant1
# dajun在tenant2中的角色是developer
g, dajun, developer, tenant2
然后我们编写主程序
func check(e *casbin.Enforcer, sub, domain, obj, act string) {
ok, _ := e.Enforce(sub, domain, obj, act)
if ok {
fmt.Printf("%s CAN %s %s in %s\n", sub, act, obj, domain)
} else {
fmt.Printf("%s CANNOT %s %s in %s\n", sub, act, obj, domain)
}
}
func main() {
e, err := casbin.NewEnforcer("./model.conf", "./policy.csv")
if err != nil {
log.Fatalf("NewEnforecer failed:%v\n", err)
}
check(e, "dajun", "tenant1", "data1", "read")
check(e, "dajun", "tenant2", "data2", "read")
}
结果不出意料:
dajun CAN read data1 in tenant1
dajun CANNOT read data2 in tenant2
ABAC模型
RBAC
模型对于实现比较规则的、相对静态的权限管理比较好用,但是例如我们需要在不同的时间段对数据data
实现不同的权限控制,比如正常的工作时间9:00-18:00
所有人都可以读写data
,其它时间只有数据所有者能读写。这种需求就需要使用ABAC
模型完成。
首先修改model
文件:
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[matchers]
m = r.sub.Hour >= 9 && r.sub.Hour < 18 || r.sub.Name == r.obj.Owner
[policy_effect]
e = some(where (p.eft == allow))
ABAC
模型不需要策略文件:
type Object struct {
Name string
Owner string
}
type Subject struct {
Name string
Hour int
}
func check(e *casbin.Enforcer, sub Subject, obj Object, act string) {
ok, _ := e.Enforce(sub, obj, act)
if ok {
fmt.Printf("%s CAN %s %s at %d:00\n", sub.Name, act, obj.Name, sub.Hour)
} else {
fmt.Printf("%s CANNOT %s %s at %d:00\n", sub.Name, act, obj.Name, sub.Hour)
}
}
func main() {
e, err := casbin.NewEnforcer("./model.conf", "./policy.csv")
if err != nil {
log.Fatalf("NewEnforecer failed:%v\n", err)
}
o := Object{"data", "dajun"}
s1 := Subject{"dajun", 10}
check(e, s1, o, "read")
s2 := Subject{"lizi", 10}
check(e, s2, o, "read")
s3 := Subject{"dajun", 20}
check(e, s3, o, "read")
s4 := Subject{"lizi", 20}
check(e, s4, o, "read")
}
dajun CAN read data at 10:00
lizi CAN read data at 10:00
dajun CAN read data at 20:00
lizi CANNOT read data at 20:00
使用ABAC
模型可以非常灵活的进行权限控制,但是在一般情况的RBAC
就够用了。
模型存储
casbin可以实现在代码中动态初始化模型:
func main() {
m := model.NewModel()
m.AddDef("r", "r", "sub, obj, act")
m.AddDef("p", "p", "sub, obj, act")
m.AddDef("e", "e", "some(where (p.eft == allow))")
m.AddDef("m", "m", "r.sub == g.sub && r.obj == p.obj && r.act == p.act")
a := fileadapter.NewAdapter("./policy.csv")
e, err := casbin.NewEnforcer(m, a)
if err != nil {
log.Fatalf("NewEnforecer failed:%v\n", err)
}
check(e, "dajun", "data1", "read")
check(e, "lizi", "data2", "write")
check(e, "dajun", "data1", "write")
check(e, "dajun", "data2", "read")
}
或者在字符串中加载
func main() {
text := `
[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
`
m, _ := model.NewModelFromString(text)
a := fileadapter.NewAdapter("./policy.csv")
e, _ := casbin.NewEnforcer(m, a)
check(e, "dajun", "data1", "read")
check(e, "lizi", "data2", "write")
check(e, "dajun", "data1", "write")
check(e, "dajun", "data2", "read")
}
但是这两种方式都不推荐使用
策略存储
在前面的例子中,我们都是将策略存储在一个.csv文件中,但是实际应用中一般不用这种方式存储,casbin
以第三方适配器的方式支持多种存储方式,包括Mysql/MongoDB/Redis/Etcd
等。
CREATE DATABASE IF NOT EXISTS casbin;
USE casbin;
CREATE TABLE IF NOT EXISTS casbin_rule (
p_type VARCHAR(100) NOT NULL,
v0 VARCHAR(100),
v1 VARCHAR(100),
v2 VARCHAR(100),
v3 VARCHAR(100),
v4 VARCHAR(100),
v5 VARCHAR(100)
);
INSERT INTO casbin_rule VALUES
('p', 'dajun', 'data1', 'read', '', '', ''),
('p', 'lizi', 'data2', 'write', '', '', '');
然后使用Gorm Adapter
加载policy
,Gorm Adapter
默认使用casbin
库中的casbin_rule
表:
package main
import (
"fmt"
"github.com/casbin/casbin/v2"
gormadapter "github.com/casbin/gorm-adapter/v2"
_ "github.com/go-sql-driver/mysql"
)
func check(e *casbin.Enforcer, sub, obj, act string) {
ok, _ := e.Enforce(sub, obj, act)
if ok {
fmt.Printf("%s CAN %s %s\n", sub, act, obj)
} else {
fmt.Printf("%s CANNOT %s %s\n", sub, act, obj)
}
}
func main() {
a, _ := gormadapter.NewAdapter("mysql", "root:12345@tcp(127.0.0.1:3306)/")
e, _ := casbin.NewEnforcer("./model.conf", a)
check(e, "dajun", "data1", "read")
check(e, "lizi", "data2", "write")
check(e, "dajun", "data1", "write")
check(e, "dajun", "data2", "read")
}
运行:
dajun CAN read data1
lizi CAN write data2
dajun CANNOT write data1
dajun CANNOT read data2
使用函数
我们可以在匹配器中使用函数。casbin
内置了一些函数keyMatch/keyMatch2/keyMatch3/keyMatch4
都是匹配 URL 路径的,regexMatch
使用正则匹配,ipMatch
匹配 IP 地址。参见https://casbin.org/docs/en/function。使用内置函数我们能很容易对路由进行权限划分:
[matchers]
m = r.sub == p.sub && keyMatch(r.obj, p.obj) && r.act == p.act
p, dajun, user/dajun/*, read
p, lizi, user/lizi/*, read
不同用户只能访问其对应路由下的 URL:
func main() {
e, err := casbin.NewEnforcer("./model.conf", "./policy.csv")
if err != nil {
log.Fatalf("NewEnforecer failed:%v\n", err)
}
check(e, "dajun", "user/dajun/1", "read")
check(e, "lizi", "user/lizi/2", "read")
check(e, "dajun", "user/lizi/1", "read")
}
输出:
dajun CAN read user/dajun/1
lizi CAN read user/lizi/2
dajun CANNOT read user/lizi/1
我们当然也可以定义自己的函数。先定义一个函数,返回 bool:
func KeyMatch(key1, 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
}
然后添加到权限认证器中:
e.AddFunction("my_func", KeyMatchFunc)
这样我们就可以在匹配器中使用该函数实现正则匹配了:
[matchers]
m = r.sub == p.sub && my_func(r.obj, p.obj) && r.act == p.act
接下来我们在策略文件中为dajun
赋予权限:
p, dajun, data/*, read
dajun
对匹配模式data/*
的文件都有read
权限。
验证一下:
check(e, "dajun", "data/1", "read")
check(e, "dajun", "data/2", "read")
check(e, "dajun", "data/1", "write")
check(e, "dajun", "mydata", "read")
dajun
对data/1
没有write
权限,mydata
不符合data/*
模式,也没有read
权限:
dajun CAN read data/1
dajun CAN read data/2
dajun CANNOT write data/1
dajun CANNOT read mydata
参考资料
欢迎评论区讨论,或指出问题。 如果觉得写的不错,欢迎点赞,转发,收藏。