casbin学习笔记

news/2024/7/24 5:10:55 标签: go, casbin, 权限控制, RBAC, ABAC

与你相识


博主介绍:

– 本人是普通大学生一枚,每天钻研计算机技能,CSDN主要分享一些技术内容,因我常常去寻找资料,不经常能找到合适的,精品的,全面的内容,导致我花费了大量的时间,所以会将摸索的内容全面细致记录下来。另外,我更多关于管理,生活的思考会在简书中发布,如果你想了解我对生活有哪些反思,探索,以及对管理或为人处世经验的总结,我也欢迎你来找我。

– 目前的学习专注于Go语言,辅学算法,前端领域。也会分享一些校内课程的学习,例如数据结构,计算机组成原理等等,如果你喜欢我的风格,请关注我,我们一起成长。


Table of Contents

  • casbin
    • 官方文档
      • 基础知识
        • 安装及简单实用
        • 工作原理
      • Model
        • 支持的Model
        • Model语法
          • Request定义
          • Policy定义
          • Policy effect定义
          • Matchers
            • 内置函数
        • RBAC
        • ABAC
    • 每日一库——casbin
      • ACL模型
      • RBAC模型
      • ABAC模型
      • 模型存储
      • 策略存储
        • 使用函数
    • 参考资料


casbin_21">casbin

权限管理几乎在每个系统中都是必备的模块。如果项目开发每次都要实现一次权限管理,会浪费很多的时间,而casbin就来做这个事情,支持常用的多种访问控制模型,如ACL/RBAC/ABAC等。可以实现灵活的访问权限控制

比如说什么角色,什么用户,可以通过某个api,就可以使用casbin来进行限制。

官方文档

由于官方文档到中间靠后的部分有点难以理解,所以就转向其它教程了。

基础知识

安装及简单实用

go">go get github.com/casbin/casbin

另外创建一个Casbin决策器需要有一个模型文件和策略文件作为参数:

go">import "github.com/casbin/casbin"

e := casbin.NewEnforcer("path/to/model.conf", "path/to/policy.csv")

然后可以在访问发生之前,给代码添加强制挂钩:

go">sub := "alice" // 想要访问资源的用户。
obj := "data1" //要访问的资源。
act := "read" // 用户对资源执行的操作。

if e.Enforce(sub, obj, act) == true {
    // 允许alice读取data1
} else {
    // 拒绝请求,显示错误
}

工作原理

casbin的访问控制模型被抽象为基于PERM(Policy(策略), Effect(效果), Request(请求), Matcher(匹配器))的一个文件。

Casbin中最基本,最简单的model是ACLACL中的model CONF为:

go">  # 请求定义
    [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如下:

go">// 这表示alice可以读取data1
    p, alice, data1, read
// 这表示bob可以编写data2
    p, bob, data2, write

Model

支持的Model

  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语法

  • Model CONF 至少应包含四个部分:[request_definition], [policy_definition], [policy_effect], [matchers]
  • 如果 model 使用 RBAC, 还需要添加[role_definition]部分。
Request定义

[request_definition]部分用于request的定义,它明确了e.Enforce(...)函数中参数的含义。

go">[request_definition]
r = sub, obj, act

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

Policy定义

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

go">[policy_definition]
p = sub, obj, act

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

go">p, alice, data1, read

policy部分的每一行称之为一个策略规则。 上面的policy的绑定关系将会在matcher中使用, 罗列如下:

go">(alice, data1, read) -> (p.sub, p.obj, p.act)
Policy effect定义

[policy_effect]部分是对policy生效范围的定义, 原语定义了当多个policy rule同时匹配访问请求request时,该如何对多个决策结果进行集成以实现统一决策。 以下示例展示了一个只有一条规则生效,其余都被拒绝的情况:

go">[policy_effect]
e = some(where (p.eft == allow))

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

这是另一个policy effect的例子:

go">[policy_effect]
e = !some(where (p.eft == deny))

该Effect原语表示不存在任何决策结果为deny的匹配规则,则最终决策结果为allow,即deny-override。some量词判断是否存在一条策略规则满足匹配器。另外any量词则判断是否所有的策略规则都满足匹配器。 policy effect还可以利用逻辑运算符进行连接:

go">[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_201">RBAC

看不懂

ABAC_205">ABAC

看不懂

casbin_209">每日一库——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根据对请求运用匹配器得出的所有结果进行汇总,来决定该请求是允许还是拒绝

编写模型文件:

go">[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对数据data1read权限,lizi对数据data2write权限。

接下来就是使用的代码:

go">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">$ 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"没有对data1write权限,第 4 个因为dajundata2没有read权限,所以检查都不能通过。输出结果符合预期。

sub/obj/act依次对应传给Enforce方法的三个参数。实际上这里的sub/obj/actread/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_348">RBAC模型

ACL在用户和资源比较少的情况下没有什么问题,但是如果用户很多的话,就会显得非常的繁琐,试想一下每次新增一个用户,都要把他需要的权限重新设置一遍是多么地痛苦。

所以RBAC就通过引入角色(role)来解决这个问题,每个用户都属于一个角色,每个角色都有其特定的权限,这样新增用户的时候,只需要给他指派一个角色,他就可以拥有该角色对应的权限信息。

使用RBAC模型需要在ACLMODEL的基础上添加role_definition模块:

g = _,_定义了用户——角色,角色——角色的映射关系,前者是后者的成员,拥有后者的权限。然后在匹配器中,我们不需要判断r.subp.sub是否相当,只需要使用g(r.sub, p.sub)来判断请求主体r.sub是否属于p.sub这个角色即可。

go">[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

我们编写主程序

go">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")
}

结果:

go">dajun CAN read data
dajun CAN write data
lizi CAN read data
lizi CANNOT write data

RBAC_421">多个RBAC

可以为用户和资源都赋予角色的概念

go">[role_definition]
g=_,_
g2=_,_

[matchers]
m = g(r.sub, p.sub) && g2(r.obj, p.obj) && r.act == p.act

上面的模型文件定义了两个RBAC系统gg2,我们在匹配器中使用g(r.sub, p.sub)判断请求主体属于特定组,g2(r.obj, p.obj)判断请求资源属于特定组,且操作一致即可放行。

go">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角色拥有对proddev类资源的读写权限,developer只能拥有对dev的读写权限和prod的读权限。

go">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")
go">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属于高级开发者seniorseinor属于开发者,那么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_489">RBAC domain

角色可以是全局的,也可以是特定域(domain)的或租户(tenant),可以理解为

例如dajun在组tenant1中是管理员,拥有很高的权限,但是在tenant2中可能只是一个底层人员。

使用RBAC domain需要对模型文件做以下修改:

go">[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

然后我们编写主程序

go">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_554">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模型不需要策略文件:

go">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可以实现在代码中动态初始化模型:

go">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")
}

或者在字符串中加载

go">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加载policyGorm Adapter默认使用casbin库中的casbin_rule表:

go">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:

go">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:

go">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{}类型包装一层:

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

dajundata/1没有write权限,mydata不符合data/*模式,也没有read权限:

dajun CAN read data/1
dajun CAN read data/2
dajun CANNOT write data/1
dajun CANNOT read mydata

参考资料

  • casbin中文开发文档(官方)
  • 每日一库——casbin

欢迎评论区讨论,或指出问题。 如果觉得写的不错,欢迎点赞,转发,收藏。


http://www.niftyadmin.cn/n/1736533.html

相关文章

python module ‘mitmproxy.proxy‘ has no attribute ‘config‘问题解决

python module ‘mitmproxy.proxy’ has no attribute config’问题解决 原因是mitmproxy的版本太高&#xff0c;目前7.x的版本已经不是这样解决了&#xff0c;使用下面的语句降一下版本即可&#xff1a; pip install mitmproxy6.0.2 -i http://pypi.douban.com/simple/

001问 | 为什么Go语言的for循环结构体不能用var?

偶然间&#xff0c;我的好朋友问起我这个问题&#xff0c;我还真没有好好思考过这个问题。 我查了Go语言圣经&#xff0c;知名博客&#xff0c;以及Go中文文档&#xff0c;发现都没有阐述这个问题。 后来找来找去也只有下面的解释&#xff1a; for的初始化语句不能是任何类型…

002问 | 在Go语言中fmt.Println和println的区别?

它们的区别是什么&#xff1f; 我还真没思考过这个问题。 可以看到它们的颜色不同&#xff0c;fmt.Println输出到标准输出&#xff0c;而println输出到标准错误。log标准包里的打印函数会默认写入标准错误。println主要程序启动和调试的时候用&#xff0c;应该是Go语言内部的实…

IT大学生成长周报 | 发刊词

目录 文章目录目录IT大学生成长周报发刊词IT大学生成长周报发刊词 想长期做这样一件事情&#xff1a; 每天搜集一些资料&#xff0c;看一些文章&#xff0c;主要方向是Go语言&#xff0c;It趋势和发展&#xff0c;社会时事&#xff0c;然后把这些文章我觉得比较好的收集整理起…

IT大学生成长周报 | 第 1 期

文章目录IT大学生成长周报&#xff08;第 1 期&#xff09;编程语言插桩Linux高性能服务器代码学习关于Mysql锁的一些知识和试验一道关于array的题目go-spewMysql为什么这么重要&#xff1f;Go编程模式&#xff1a;详解函数式选项模式Go 1.18 中的 any 是什么&#xff1f;Go&am…

单片机项目——电子表项目逐句解析

电路图&#xff1a; 程序设计 // main.c /**电子表设计程序分析author: 胡云飞程序步骤如下&#xff1a;1. 初始化定时器2. 写中断服务函数- 初始化计时器的初值- 让cp&#xff0c;当cp>250时&#xff0c;为半秒&#xff0c;让cp2&#xff0c;flash取反&#xff08;分隔符闪…

单片机项目——温度计项目逐句解析

电路图 程序设计 主要程序。 // main.c #include<reg51.h> #include<DS18B20.c> unsigned char cp1,cp2,cp3; char temp; bit flag; unsigned char seven_seg[] {0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90, }; void timer0_isr(void) interrupt 1 {…

IT大学生成长周报 | 第 2 期

文章目录IT大学生成长周报&#xff08;第 2 期&#xff09;编程语言自上而下理解容器学习微服务的方法Go动态类型腾讯三面&#xff1a;40亿个QQ号码如何去重&#xff1f;奇葩的编程语言Go1.18 Beta1 发布云计算的安全问题——阿里云Golang开发新手常犯的50个错误腾讯43亿qq号码…