天天看点

casbin学习笔记casbin

与你相识

casbin学习笔记casbin

博主介绍:

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

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

Table of Contents

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

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

  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(...)

函数中参数的含义。

[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中使用, 罗列如下:

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:[email protected](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
           

参考资料

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

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