go-admin后台设计之casbin权限管理

1.概要

权限管理⼏乎是每个系统或者服务都会直接或者间接涉及的部分.权限管理保障了资源(⼤部分时候就是数据)的安全,权限管理⼀般都是和业务强关联,每当有新的业务或者业务变化时,不能将精⼒完全放在业务实现上,权限的调整往往耗费⼤量的精⼒.其实,权限的本质没有那么复杂,只是对访问的控制⽽已,有⼀套完善的访问控制接⼝,再加上简单的权限模型.权限模型之所以能够简单,就是因为权限管理本身并不复杂,只是在和具体业务结合时,出现了各种各样的访问控制场景,才显得复杂.

2.PERM模型

PERM(Policy,Effect,Request,Matchers)模型很简单,但是反映了权限的本质–访问控制

Policy:定义权限的规则

Effect:定义组合了多个Policy之后的结果,allow/deny

Request:访问请求,也就是谁想操作什么

Matcher:判断Request是否满⾜Policy

3.casbin权限库

casbin使⽤了PERM模型来表达权限,是⼀个⽤Go语⾔打造的轻量级开源访问控制框架(https://github.com/hsluoyz/casbin),⽬前在GitHub开源。casbin采⽤了元模型的设计思想,⽀持多种经典的访问控制⽅案,如基于⻆⾊的访问控制RBAC、基于属性的访问控制ABAC等。

casbin的主要特性

1.⽀持⾃定义请求的格式,默认的请求格式为{subject,object,action};

2.具有访问控制模型model和策略policy两个核⼼概念;

3.⽀持RBAC中的多层⻆⾊继承,不⽌主体可以有⻆⾊,资源也可以具有⻆⾊;

4.⽀持超级⽤户,如root或Administrator,超级⽤户可以不受授权策略的约束访问任意资源;

5.⽀持多种内置的操作符,如keyMatch,⽅便对路径式的资源进⾏管理,如/foo/bar可以映射到/foo*;

casbin不做的事情

1.身份认证authentication(即验证⽤户的⽤户名、密码),casbin只负责访问控制。应该有其他专⻔的组件负责身份认证,然后由casbin进⾏访问控制,⼆者是相互配合的关系;

2.管理⽤户列表或⻆⾊列表。casbin认为由项⽬⾃身来管理⽤户、⻆⾊列表更为合适,casbin假设所有策略和请求中出现的⽤户、⻆⾊、资源都是合法有效的。

核⼼概念

modelfile

⽤来定义具体的权限模型,⽬前⽀持的模型基本覆盖了常⻅的所有场景:

1.ACL(AccessControlList)

2.ACLwithsuperuser(超级⽤户)

3.ACLwithoutusers

4.ACLwithoutresources

5.RBAC(Role-BasedAccessControl:基于⻆⾊的访问控制)

6.RBACwithresourceroles

7.RBACwithdomains/tenants

8.ABAC

9.……

modelfile定义语法

casbin是基于PERM的,所有modelfile中主要就是定义PERM4个部分.

1.Requestdefinition

[request_definition]

r=sub,obj,act

分别表示request中的

accessingentity(Subject)

accessedresource(Object)

theaccessmethod(Action)

2.Policydefinition

[policy_definition]

p=sub,obj,act

p2=sub,act

定义的每⼀⾏称为policyrule,p,p2是policyrule的名字.p2定义的是sub所有的资源都能执⾏act 

3.Policyeffect

[policy_effect]

e=some(where(p.eft==allow))

上⾯表示有任意⼀条policyrule满⾜,则最终结果为allow 

4.Matchers

[matchers]

m=r.sub==p.sub&&r.obj==p.obj&&r.act==p.act

定义了request和policy匹配的⽅式,p.eft是allow还是deny,就是基于此来决定的

5.Role 

[role_definition]

g=_,_

g2=_,_

g3=_,_,_

g,g2,g3表示不同的RBAC体系,_,_表示⽤户和⻆⾊_,_,_表示⽤户,⻆⾊,域(也就是租户) 

policyfile

定义具体的策略,权限的检查就是基于定义的modelfile和policyfile来完成的.相对于modelfile定义规则,policyfile中定义的就是具体的内容.

定义modelfile

[request_definition]

r=sub,obj,act

[policy_definition]

p=sub,obj,act

[role_definition]

g=_,_

[policy_effect]

e=some(where(p.eft==allow))

[matchers]

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

定义policyfile 

 p,superAdmin,project,read

 p,superAdmin,project,write

 p,admin,project,read

p,admin,project,write

p,admin,asse,read

p,admin,asse,write

p,zhuangjia,project,write

p,zhuangjia,asse,write

p,shangshang,project,read

p,shangshang,asse,read

g,quyuan,admin

g,wenyin,zhuangjia 

 测试代码

// demo1.go
package main

import (
	"fmt"
	"log"

	"github.com/casbin/casbin"
)

func TestRBAC() {
	e := casbin.NewEnforcer("demo1.conf", "demo1.csv")
	fmt.Printf("RBAC test start\n") // output for debug
	// superAdmin
	if e.Enforce("superAdmin", "project", "read") {
		log.Println("superAdmin can read project")
	} else {
		log.Fatal("ERROR: superAdmin can not read project")
	}
	if e.Enforce("superAdmin", "project", "write") {
		log.Println("superAdmin can write project")
	} else {
		log.Fatal("ERROR: superAdmin can not write project")
	}

	if e.Enforce("quyuan", "project", "read") {
		log.Println("quyuan can read project")
	} else {
		log.Fatal("ERROR: quyuan can not read project")
	}
	if e.Enforce("quyuan", "project", "write") {
		log.Println("quyuan can write project")
	} else {
		log.Fatal("ERROR: quyuan can not write project")
	}
	if e.Enforce("quyuan", "asse", "read") {
		log.Println("quyuan can read asse")
	} else {
		log.Fatal("ERROR: quyuan can not read asse")
	}
	if e.Enforce("quyuan", "asse", "write") {
		log.Println("quyuan can write asse")
	} else {
		log.Fatal("ERROR: quyuan can not write asse")
	}
	// zhuangjia
	if e.Enforce("wenyin", "project", "read") {
		log.Fatal("ERROR: wenyin can read project")
	} else {
		log.Println("wenyin can not read project")
	}
	if e.Enforce("wenyin", "project", "write") {
		log.Println("wenyin can write project")
	} else {
		log.Fatal("ERROR: wenyin can not write project")
	}
	if e.Enforce("wenyin", "asse", "read") {
		log.Fatal("ERROR: wenyin can read asse")
	} else {
		log.Println("wenyin can not read asse")
	}
	if e.Enforce("wenyin", "asse", "write") {
		log.Println("wenyin can write asse")
	} else {
		log.Fatal("ERROR: wenyin can not write asse")
	}
	// shangshang
	if e.Enforce("shangshang", "project", "read") {
		log.Println("shangshang can read project")
	} else {
		log.Fatal("ERROR: shangshang can not read project")
	}
	if e.Enforce("shangshang", "project", "write") {
		log.Fatal("ERROR: shangshang can write project")
	} else {
		log.Println("shangshang can not write project")
	}
	if e.Enforce("shangshang", "asse", "read") {
		log.Println("shangshang can read asse")
	} else {
		log.Fatal("ERROR: shangshang can not read asse")
	}
	if e.Enforce("shangshang", "asse", "write") {
		log.Fatal("ERROR: shangshang can write asse")
	} else {
		log.Println("shangshang can not write asse")
	}
}

func main() {
	TestRBAC()
}

多租户示例

定义modelfile

[request_definition]

r = sub, obj, act

[policy_definition]

p = sub, obj, act

[role_definition]

g = _, _

[policy_effect]

e = some(where (p.eft == allow))

[matchers]

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

定义policyfile

p, superAdmin, gy, project, read
p, superAdmin, gy, project, write
p, superAdmin, jn, project, read
p, superAdmin, jn, project, write
p, admin, gy, project, read
p, admin, gy, project, write
p, admin, jn, asse, read
p, admin, jn, asse, write
p, zhuangjia, jn, project, write
p, zhuangjia, gy, asse, write
g, quyuan, admin, gy
g, quyuan, admin, jn
g, wenyin, zhuangjia, gy
g, shangshang, zhuangjia, jn

 测试代码

// demo1.go
package main

import (
	"fmt"
	"log"

	"github.com/casbin/casbin"
)

func TestTenants() {
	e := casbin.NewEnforcer("demo2.conf", "demo2.csv")
	fmt.Printf("RBAC TENANTS test start\n") // output for debug
	// superAdmin
	if e.Enforce("superAdmin", "gy", "project", "read") {
		log.Println("superAdmin can read project in gy")
	} else {
		log.Fatal("ERROR: superAdmin can not read project in gy")
	}
	if e.Enforce("superAdmin", "gy", "project", "write") {
		log.Println("superAdmin can write project in gy")
	} else {
		log.Fatal("ERROR: superAdmin can not write project in gy")
	}
	if e.Enforce("superAdmin", "jn", "project", "read") {
		log.Println("superAdmin can read project in jn")
	} else {
		log.Fatal("ERROR: superAdmin can not read project in jn")
	}
	if e.Enforce("superAdmin", "jn", "project", "write") {
		log.Println("superAdmin can write project in jn")
	} else {
		log.Fatal("ERROR: superAdmin can not write project in jn")
	}
	// admin
	if e.Enforce("quyuan", "gy", "project", "read") {
		log.Println("quyuan can read project in gy")
	} else {
		log.Fatal("ERROR: quyuan can not read project in gy")
	}
	if e.Enforce("quyuan", "gy", "project", "write") {
		log.Println("quyuan can write project in gy")
	} else {
		log.Fatal("ERROR: quyuan can not write project in gy")
	}
	if e.Enforce("quyuan", "jn", "project", "read") {
		log.Fatal("ERROR: quyuan can read project in jn")
	} else {
		log.Println("quyuan can not read project in jn")
	}
	if e.Enforce("quyuan", "jn", "project", "write") {
		log.Fatal("ERROR: quyuan can write project in jn")
	} else {
		log.Println("quyuan can not write project in jn")
	}
	if e.Enforce("quyuan", "gy", "asse", "read") {
		log.Fatal("ERROR: quyuan can read asse in gy")
	} else {
		log.Println("quyuan can not read asse in gy")
	}
	if e.Enforce("quyuan", "gy", "asse", "write") {
		log.Fatal("ERROR: quyuan can write asse in gy")
	} else {
		log.Println("quyuan can not write asse in gy")
	}
	if e.Enforce("quyuan", "jn", "asse", "read") {
		log.Println("quyuan can read asse in jn")
	} else {
		log.Fatal("ERROR: quyuan can not read asse in jn")
	}
	if e.Enforce("quyuan", "jn", "asse", "write") {
		log.Println("quyuan can write asse in jn")
	} else {
		log.Fatal("ERROR: quyuan can not write asse in jn")
	}
	// wenyin
	if e.Enforce("wenyin", "gy", "asse", "write") {
		log.Println("wenyin can write asse in gy")
	} else {
		log.Fatal("ERROR: wenyin can not write asse in gy")
	}
	if e.Enforce("wenyin", "jn", "asse", "write") {
		log.Fatal("ERROR: wenyin can write asse in jn")
	} else {
		log.Println("wenyin can not write asse in jn")
	}
	// shangshang
	if e.Enforce("shangshang", "jn", "project", "write") {
		log.Println("shangshang can write project in jn")
	} else {
		log.Fatal("ERROR: shangshang can not write project in jn")
	}
	if e.Enforce("shangshang", "gy", "project", "write") {
		log.Fatal("ERROR: shangshang can write project in gy")
	} else {
		log.Println("shangshang can not write project in gy")
	}
}

func main() {
	TestTenants()
}

gin+gorm+casbin示例 

// casbin-gin-gorm.go
package main

import (
	"fmt"

	"github.com/casbin/casbin"
	gormadapter "github.com/casbin/gorm-adapter"
	"github.com/gin-gonic/gin"
	_ "github.com/go-sql-driver/mysql"
)

func main() {

	a := gormadapter.NewAdapter("mysql", "root:123456@tcp(127.0.0.1:3306)/gorm1", true)
	e := casbin.NewEnforcer("casbin.conf", a)
	e.AddPolicy("admin", "/api/v1/aa", "GET")
	//从DB加载策略
	e.LoadPolicy()

	//获取router路由对象
	r := gin.New()
	//使用自定义拦截器中间件
	r.Use(AuthRoleHandler(e))
	//创建请求
	r.GET("/api/v1/aa", func(c *gin.Context) {
		fmt.Println("Hello 接收到/api/v1/aa GET请求..")
		c.String(200, "已授权")
	})

	r.GET("/api/v1/bb", func(c *gin.Context) {
		fmt.Println("Hello 接收/api/v1/bb 到GET请求..")
		c.String(200, "已授权")
	})
	r.Run(":8080") //参数为空 默认监听8080端口
}

//拦截器
func AuthRoleHandler(e *casbin.Enforcer) gin.HandlerFunc {

	return func(c *gin.Context) {

		//获取请求的URI
		obj := c.Request.URL.RequestURI()
		//获取请求方法
		act := c.Request.Method
		//获取用户的角色
		sub := "admin"

		//判断策略中是否存在
		if e.Enforce(sub, obj, act) {
			fmt.Println("通过权限")
			c.Next()
		} else {
			fmt.Println("权限没有通过")
			c.String(200, "没有权限")
			c.Abort()
		}
	}
}

4总结

casbin权限管理库⽐较简单,易上⼿,但是它的功能却不简单,⽀持了⽬前主流的所有权限管理场景。在使⽤上,modelfile和poclicyfile的定义也简单明了,抽象出了权限管理最本质的东⻄.将具体业务中的权限要求映射到casbin中modelfile,就可以借助casbin的API,快速的实现权限管理。

更多使⽤⽅式参考:go-admin。

5参考⽂档

RBAC权限系统分析、设计与实现

https://shuwoom.com/?p=3041

B端系统的权限管理设计

https://zhuanlan.zhihu.com/p/109735749

Go语⾔gin框架集成Casbin实现访问权限控制

https://blog.csdn.net/weixin_37717557/article/details/108983361Golang

最强⼤的访问控制框架casbin全解析

https://www.cnblogs.com/yjf512/p/12200206.html

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值