Golang访问权限控制框架casbin

官方文档

https://casbin.org/docs/zh-CN/overview

casbin简介

Casbin 是一个强大的、高效的开源访问控制框架,其权限管理机制支持多种访问控制模型。支持的语言也很多,例如:go、java、node.js、python等等.

Casbin 是什么?

Casbin 可以:

{subject, object, action}rootadministratorkeyMatch/foo/bar/foo*

Casbin 不能:

  1. 身份认证 authentication(即验证用户的用户名和密码),Casbin 只负责访问控制。应该有其他专门的组件负责身份认证,然后由 Casbin 进行访问控制,二者是相互配合的关系。
  2. 管理用户列表或角色列表。 Casbin 认为由项目自身来管理用户、角色列表更为合适, 用户通常有他们的密码,但是 Casbin 的设计思想并不是把它作为一个存储密码的容器。 而是存储RBAC方案中用户和角色之间的映射关系。

访问控制模型

控制访问模型有哪几种?我们需要先来了解下这个。

setfacl -m user:yejianfeng:rw- ./test
[yejianfeng@ipd-itstool ~]$ getfacl test
# file: test
# owner: yejianfeng
# group: yejianfeng
user::rw-
user:yejianfeng:rw-
group::rw-
mask::rw-
other::r--
Bob 可以在命名空间 projectCaribou 中读取 pod:
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"user": "bob", "namespace": "projectCaribou", "resource": "pods", "readonly": true}}

简单使用

安装

go get github.com/casbin/casbin
go get github.com/casbin/casbin/v2 // 可能兼容还有问题

go get github.com/casbin/gorm-adapter

go get github.com/go-sql-driver/mysql

go get github.com/jinzhu/gorm

创建model配置文件

model.conf

# 请求定义
[request_definiation]
r = sub,obj,act
# sub ——> 想要访问资源的用户角色(Subject)——请求实体
# obj ——> 访问的资源(Object)
# act ——> 访问的方法(Action: get、post...)


# 策略定义
# 策略(.csv文件p的格式,定义的每一行为policy rule;p,p2为policy rule的名字。)
[policy_definiation]
p = sub,obj,act
# p2 = sub,act 表示sub对所有资源都能执行act


# 组定义
[role_definiation]
g = _, _
# g = _,_定义了用户——角色,角色——角色的映射关系,前者是后者的成员,拥有后者的权限。
# _,_表示用户,角色/用户组


# 策略效果
[policy_effect]
e = some(where(p.eft = allow))
# 上面表示有任意一条 policy rule 满足, 则最终结果为 allow;p.eft它可以是allow或deny,它是可选的,默认是allow

# 匹配器
[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act

# 上面模型文件规定了权限由sub,obj,act三要素组成,只有在策略列表中有和它完全相同的策略时,该请求才能通过。

policy文件(policy.csv)

p,zxp,data1,read
p,zhang,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() {
	//首先创建一个casbin.Enforcer对象,加载模型文件model.conf和策略文件policy.csv,调用其		Enforce方法来检查权限
  e, err := casbin.NewEnforcer("./model.conf", "./policy.csv")
  if err != nil {
    log.Fatalf("NewEnforecer failed:%v\n", err)
  }
  check(e, "zxp", "data1", "read")
  check(e, "zhang", "data2", "write")
  check(e, "zxp", "data1", "write")
  check(e, "zxp", "data2", "read")
}

测试结果:

zxp CAN read data1
zhang CAN write data2
zxp CANNOT write data1
zxp CANNOT read data2

超级管理员实现

[matchers]
e = r.sub == p.sub && r.obj == p.obj && r.act == p.act || r.sub == "root"

g = _, _的用法

g = _,__定义了用户——角色或角色——角色的映射关系,前者是后者的成员,拥有后者的权限。

model文件配置为

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

policy文件配置为

p, admin, data, read
p, admin, data, write
p, developer, data, read
g, zxp, admin
g, zhang, developer
admindatareadwritedeveloperdataread
zxpzhang

多个g _ , _

model文件配置改为:

[role_definition]
g=_,_
g2=_,_

[matchers]
m = g(r.sub, p.sub) && g2(r.obj, p.obj) && r.act == p.act
# 只改这两个,其他不变

policy文件举例如下:

p, admin, data1, read
p, admin, data1, write
p, admin, data2, read
p, admin, data2, write
p, developer, data2, read
p, developer, data2, write
p, developer, data1, read
g, zxp, admin
g, zhang, developer
g2, data1.data, data1
g2, data2.data, data2
zxpadmindata1data2
zhangdeveloperdata1data2
data1.datadata1zxpzhangdata1.data
data2.datadata2zhang

多层角色

model文件不用修改

policy文件举例如下:

p, senior, data, write
p, developer, data, read
g, zxp, senior
g, senior, developer
g, zhang, developer
seniordatawrite
developerreadseniordeveloperseniorread
zxpdata

domain领域

model文件修改如下

[request_definition]
r = sub, dom, obj, act

[policy_definition]
p = sub, dom, obj, act

[role_definition]
g = _,_,_
# g2 = _,_,_ 表示用户, 角色/用户组, 域(也就是租户)


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

policy文件举例如下:

p, admin, dom1, data1, read
p, admin, dom2, data2, read
g, zxp, admin, dom1
dom1admindata1
dom2admindata2
zxpdata1admindata2

代码实现

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", "dom1", "data1", "read")
  check(e, "dajun", "dom2", "data2", "read")
}

动态控制可读可写

根据时间控制

model文件修改如下

[matchers]
m = r.sub.Hour >= 5 && r.sub.Hour < 20 || r.sub.Name == r.obj.Owner> 

注意:该模式下不需要policy文件配置

代码实现

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

  //r.sub.Hour < 18 || r.sub.Name == r.obj.Owner 这两个满足一个就可读
  o := Object{"data", "zxp"}
    
    
  s1 := Subject{"zxp", 10}
  check(e, s1, o, "read")//可读

  s2 := Subject{"zhang", 10}
  check(e, s2, o, "read")//可读

  s4 := Subject{"zhang", 20}
  check(e, s4, o, "read")//不可读
    
}

模式存储

动态初始化model文件(代码内实现model)

package main

import (
	"fmt"
	"log"

	fileadapter "github.com/casbin/casbin/persist/file-adapter"
	"github.com/casbin/casbin/v2"
	"github.com/casbin/casbin/v2/model"
)

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() {
	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, "zxp", "data1", "read")
	check(e, "zhang", "data2", "write")
	check(e, "zxp", "data1", "write")
	check(e, "zxp", "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)
// }

策略储存policy.csv实际运用

Gorm AdapterSQL
CREATE DATABASE IF NOT EXISTS casbin;

USE casbin;

CREATE TABLE IF NOT EXISTS casbin_rule (
  p_type VARCHAR(100) NOT NULL,
  s0 VARCHAR(100),
  s1 VARCHAR(100),
  s2 VARCHAR(100)
);

INSERT INTO casbin_rule VALUES
('p', zxp', 'data1', 'read', '', '', ''),
('p', 'zhang', 'data2', 'write', '', '', ''); 

代码实现

package main

import (
	"fmt"

	"github.com/casbin/casbin/v2"
	gormadapter "github.com/casbin/gorm-adapter"
)

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)/test_db", false)
	e, _ := casbin.NewEnforcer("./model.conf", a)

	check(e, "zxp", "data1", "read")    //可读
	check(e, "zhang", "data2", "write") //可写

}