简介

casbincasbinACL/RBAC/ABACcasbinGo/Java/Node/PHP/Python/.NET/Rust

快速使用

我们依然使用 Go Module 编写代码,先初始化:

$ mkdir casbin && cd casbin
$ go mod init github.com/darjun/go-daily-lib/casbin
复制代码
casbinv2
$ go get github.com/casbin/casbin/v2
复制代码
casbin
policy
requeste.Enforce()
matcherpolicy
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,actp.eftsome(where (p.eft == allow))

然后我们策略文件(即谁能对什么资源进行什么操作):

p, dajun, data1, read
p, lizi, data2, write
复制代码
policy.csvdajundata1readlizidata2write

接下来就是使用的代码:

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.Enforcermodel.confpolicy.csvEnforce
$ 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"dajun"data1writedajundata2read
sub/obj/actEnforcesub/obj/actread/write/data1/data2
ACLACLroot
[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 模型

ACLACLRBACrole
casbinRBACrole_definition
[role_definition]
g = _, _

[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
复制代码
g = _,_r.subp.subg(r.sub, p.sub)r.subp.sub
p, admin, data, read
p, admin, data, write
p, developer, data, read
g, dajun, admin
g, lizi, developer
复制代码
policy.csvdajunadminlizidevelopergadmindatareadwritedeveloperdataread
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")
}
复制代码
liziwrite
dajun CAN read data
dajun CAN write data
lizi CAN read data
lizi CANNOT write data
复制代码
RBAC
casbinRBAC
[role_definition]
g=_,_
g2=_,_

[matchers]
m = g(r.sub, p.sub) && g2(r.obj, p.obj) && r.act == p.act
复制代码
RBACgg2g(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
复制代码
dajunadminlizideveloperprod.dataproddev.datadevadminproddevdeveloperdevprod
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")
复制代码
e.Enforce()dajunadminprod.dataprodp, admin, prod, readlizideveloperprod.dataprod
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
复制代码

多层角色

casbindajunseniorseinordajunsenior

模型文件不用修改,策略文件改动如下:

p, senior, data, write
p, developer, data, read
g, dajun, senior
g, senior, developer
g, lizi, developer
复制代码
policy.csvseniordatawritedeveloperreadseniordeveloperseniorreaddajunseniordajundatareadwritelizideveloperdataread
check(e, "dajun", "data", "read")
check(e, "dajun", "data", "write")
check(e, "lizi", "data", "read")
check(e, "lizi", "data", "write")
复制代码
RBAC
casbindomaintenantdajuntenant1tenant2
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=_,_,_gdom
p, admin, tenant1, data1, read
p, admin, tenant2, data2, read
g, dajun, admin, tenant1
g, dajun, developer, tenant2
复制代码
tenant1admindata1tenant2admindata2dajuntenant1admintenant2
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

RBACRBACdata9:00-18:00dataABAC
[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))
复制代码

该规则不需要策略文件:

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")
}
复制代码
lizi20:00readdata
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
复制代码
model.confr.subr.objr.actEnforcesub/objgovaluatemodel.confr.sub.Namer.Obj.Ownergovaluate
ABACRBAC

模型存储

casbinget-started
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")
}
复制代码

但是这两种方式并不推荐。

策略存储

policy.csvcasbinMySQL/MongoDB/Redis/EtcdGorm AdapterSQL
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 AdapterpolicyGorm Adaptercasbincasbin_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
复制代码

使用函数

casbinkeyMatch/keyMatch2/keyMatch3/keyMatch4regexMatchipMatch
[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
复制代码
dajundata/*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/1writemydatadata/*read
dajun CAN read data/1
dajun CAN read data/2
dajun CANNOT write data/1
dajun CANNOT read mydata
复制代码

总结

casbin

大家如果发现好玩、好用的 Go 语言库,欢迎到 Go 每日一库 GitHub 上提交 issue????

参考

欢迎关注我的微信公众号【GoUpUp】,共同学习,一起进步~