casbin最牛逼的权限管理
先上链接casbin官网
casbin能做什么?
支持多种编程语言Go/Java/Node/PHP/Python/.NET/Rust,一次学习多处运用
- 支持自定义请求格式,默认格式(subject,object,action)
- 具有访问控制模型model和策略policy两个核心概念
- 支持RBAC中的多层继承,不仅subject有角色,object也可以有角色
- 支持内置的超级用户,例如root或admin,超级用户可以执行任何操作而无需显式的权限声明
- 支持多种内置的操作符,如 keyMatch,方便对路径式的资源进行管理,如 /foo/bar 可以映射到 /foo*
Casbin 不能做什么?
- 身份认证 authentication(即验证用户的用户名、密码),casbin只负责访问控制。应该有其他专门的组件负责身份认证,然后由casbin进行访问控制,二者是相互配合的关系。
- 管理用户列表或角色列表。 Casbin 认为由项目自身来管理用户、角色列表更为合适, 用户通常有他们的密码,但是 Casbin 的设计思想并不是把它作为一个存储密码的容器。 而是存储RBAC方案中用户和角色之间的映射关系
快速开始
Casbin使用配置文件来设置访问控制模式。
它有两个配置文件,model.conf
和policy.csv
。 其中,model.conf存储了访问模型,policy.csv存储了特定的用户权限配置。 Casbin的使用非常精炼。 基本上,我们只需要一个主要结构:enforcer。 当构建这个结构时,model.conf和policy.csv将被加载
mkdir demo && cd demo && go mod init github.com/51op/go-sdk-demo
go get github.com/casbin/casbin/v2
- 创建model模型文件model.conf
[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 || r.sub == "root" #只要访问主体是root一律放行。
[policy_effect]
e = some(where (p.eft == allow))
上面模型文件规定了权限由sub
,obj
,act
三要素组成,只有在策略列表中有和它完全相同的策略时,该请求才能通过。匹配器的结果可以通过p.eft
获取,some(where (p.eft == allow))表示只要有一条策略允许即可
- 创建策略控制文件policy.csv
p, demo , /user, write #demo用户对/user有write权限
p, demo , /order, read #demo用户对/order有read权限
p, demo1 , /user/userlist,read #demo1用户对/user/userlist有read权限
p, demo2 , /order/orderlist,read #demo2用户对/order/orderlist有read权限
- 检查权限
import (
"fmt"
"github.com/casbin/casbin/v2"
gormadapter "github.com/casbin/gorm-adapter/v3"
_ "github.com/go-sql-driver/mysql"
"log"
"testing"
)
func CheckPermi(e *casbin.Enforcer ,sub,obj,act string) {
ok, err := e.Enforce(sub, obj, act)
if err != nil {
return
}
if ok == true {
fmt.Printf("%s CAN %s %s\n", sub, act, obj)
} else {
fmt.Printf("%s CANNOT %s %s\n", sub, act, obj)
}
}
func TestCasBin( t *testing.T) {
e, err := casbin.NewEnforcer("./model.conf", "./policy.csv")
if err !=nil{
log.Fatalf("NewEnforecer failed:%v\n", err)
}
//基本权限设置
CheckPermi(e, "demo", "/user", "read")
CheckPermi(e, "demo", "/order", "write")
CheckPermi(e, "demo1", "/user/userlist", "read")
CheckPermi(e, "demo1", "/order/orderlist", "write")
}
可通过官网的编辑器直接运行查看结果,【直达链接】
实战项目 参考
有些代码未做抽取勿喷
- 目录结构如下:
dnspod/
├── api
│ ├── casbiniapi.go
│ ├── internal
│ │ └── model
│ │ └── casbin.go
├── config
│ ├── config.json
│ ├── config.yaml
│ └── model.conf
├── common
│ ├── global.go
├── Dockerfile
├── docs
│ ├── docs.go
│ ├── swagger.json
│ └── swagger.yaml
├── go.mod
├── go.sum
├── main.go
├── router
│ ├── handler
│ │ └── func.go
│ └── route.go
- 首先在
configs目录下创建
model.conf`文件,写入如下代码:
#此文件存储访问模型
[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 = r.sub == p.sub && r.obj == p.obj || ParamsMatch(r.obj,p.obj) && r.act == p.act
- 在
api/internal/model
目录下,创建casbin.go
文件
package model
import (
"dnspod/common"
_ "dnspod/common"
"log"
)
type CasinoModel struct {
PType string `gorm:"column:p_type" json:"p_type" form:"p_type" description:"策略类型"`
RoleId string `gorm:"column:v0" json:"role_id" form:"v0" description:"角色id"`
Path string `gorm:"column:v1" json:"path" form:"v1" description:"api路径"`
Method string `gorm:"column:v2" json:"method" form:"v2" description:"方法"`
}
func(c *CasinoModel) TableName() string {
return "casbin_rule"
}
func ( c *CasinoModel) AddPolicy() error {
if ok,_:=common.CasBin.AddPolicy(c.RoleId,c.Path,c.Method);ok==false{
return common.JsonResponse(100,"增加策略失败")
}
return common.JsonResponse(200,"增加策略成功")
}
- 在common/目录下的global.go文件中增加如下:
func InitCasbinDB() *casbin.Enforcer {
dsn:=fmt.Sprintf("%s:%s@tcp(%s)/",cfg.MySQL.Username,cfg.MySQL.Password,cfg.MySQL.Host)
adapter, _ := gormadapter.NewAdapter("mysql", dsn,)
CasBin, _ = casbin.NewEnforcer(cfg.CasBin.FilePath, adapter)
CasBin.AddFunction("ParamsMatch",ParamsMatchFunc)
CasBin.LoadPolicy()
return CasBin
}
func ParamsMatch(fullNameKey1 string,key2 string) bool {
key1 := strings.Split(fullNameKey1, "?")[0]
return util.KeyMatch2(key1,key2)
}
//注册func到casbin
func ParamsMatchFunc(args ...interface{})(interface{},error) {
name1 := args[0].(string)
name2 := args[1].(string)
return ParamsMatch(name1, name2), nil
}
-
在router/下的route.go增加请求
common.InitCasbinDB() //初始化 InitCasbinDB //Casbin权限认证 authGroup:=router.Group("/api/v1/auth") { authGroup.POST("/addPolicy",handler.AddPolicy) }
-
router/handler目录下的func.go中增加AddPolicy
//Casbin 权限管理
type CasbinInfo struct {
Path string `json:"path" form:"path"`
Method string `json:"method" form:"method"`
}
type CasbinCreateRequest struct {
RoleId string `json:"role_id" form:"role_id" description:"角色ID"`
CasbinInfos []CasbinInfo `json:"casbin_infos" description:"权限模型列表"`
}
func AddPolicy(c *gin.Context ) {
log.Printf("==========")
var params CasbinCreateRequest
c.ShouldBind(¶ms)
for _, v := range params.CasbinInfos {
log.Println(params.RoleId, v.Path, v.Method)
err := api.AddPolicyApi(params.RoleId, v.Path, v.Method)
if err != nil {
// c.JSON(http.StatusOK,gin.H{
// "res":"bad",
// })
}
}
c.JSON(http.StatusOK,gin.H{
"res":"ok",
})
}
- 在api/目录下创建casbiniapi.go文件
package api
import "dnspod/api/internal/model"
func AddPolicyApi(roleId string, path, method string) error {
p:=model.CasinoModel{
PType: "p",
RoleId: roleId,
Path: path,
Method: method,
}
p.AddPolicy()
return nil
}
最后启动项目
验证
查看mysql库里数据就有了
- 权限验证通过后处理正常业务逻辑
增加访问入口
authGroup.GET("/testPolicy",common.CasbinMiddleware(),handler.TestListPolicics)
测试业务逻辑
func TestListPolicics(c *gin.Context) {
c.JSON(http.StatusOK,common.Reponse{200,"权限通过正常的业务逻辑",""})
}
增加casbin中间件
//casbin中间件
func CasbinMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
r:=NewResponseContext(c)
path:=c.Request.URL.RequestURI() //
method:=c.Request.Method
log.Println(path,method)
//验证url权限
roleId:="admin"
ok, _ := CasBin.Enforce(roleId, path, method)
if ok {
c.Next()
}else {
c.Abort()
r.ResponseContextMsg("很遗憾,权限验证没有通过")
return
}
}
}
在当前mysql库中没有 p /api/v1/auth/testPolicy GET
策略的时候执行这个api会返回没有权限
添加api/v1/auth/addPolicy
权限
然后在执行/api/v1/auth/testPolicy
这个接口已经有权限了
-
查询角色下的权限
authGroup.POST("/listPolicy",handler.GetListPolicy) //获取当前用户下的所有策略
func GetListPolicy( c *gin.Context) {
params:=CasbinRequestRoleId{}
c.ShouldBind(¶ms)
res:=api.ListPolicyApiByRoleId(params.RoleId)
log.Println(res)
c.JSON(http.StatusOK,common.Reponse{200,"获取成功",res})
}
func ListPolicyApiByRoleId(roleId string) [][]string {
r:=model.CasinoModel{RoleId: roleId}
return r.ListPolicyByRoleId(roleId)
}
基于RBAC权限认证
model模型中要修匹配器如下:
//使用keyMatch函数,这种情况下才能匹配传递不通参数的用户来匹配不同用户的权限,/api/v1/auth/testPolicy/user1、/api/v1/auth/testPolicy/user2
[matchers]
m = g(r.sub, p.sub) && keyMatch(r.obj, p.obj) && r.act == p.act
- 增加角色
分为给用户分配单一角色
、给用户分配多个角色
//给单一用户分配单一角色
func (c *CasinoModel) AddRoleForUser(user string,roles string) error {
if ok, _ := common.CasBin.AddRoleForUser(user, roles);!ok{
return errors.New("给用户分配单一角色失败")
}
return nil
}
//给单一用户分配多个角色
func (c *CasinoModel) AddRolesForUser(user string,roles []string) error {
if ok, _ := common.CasBin.AddRolesForUser(user, roles);!ok{
return errors.New("数据库中已经对应的roles策略")
}
return nil
}
在目录./router/handler/func.go中增加路由
type UserRoleInfo struct {
UserName string `json:"user_name"`
RoleName string `json:"role_name"`
}
func AddRoleUser(c *gin.Context) {
u:= UserRoleInfo{}
c.ShouldBind(&u)
if err:=api.AddRoleForUserApi(u.UserName,u.RoleName);err !=nil{
common.ErrorResp(c,http.StatusInternalServerError,"给用户增加role失败")
return
}
common.SuccessResp(c,"给用户增加role成功")
}
//给单一用户分配多个角色
type RolesInfoRequest struct {
UserName string `json:"user_name"`
RoleName []string `json:"role_name"`
}
func AddRolesUser(c *gin.Context) {
ro:= RolesInfoRequest{}
err:=c.ShouldBind(&ro)
if err !=nil {
panic(err)
}
if err:=api.AddRolesForUserApi(ro.UserName,ro.RoleName);err!=nil{
common.ErrorResp(c,http.StatusInternalServerError,"给用户分配多个roles失败")
return
}
common.SuccessResp(c,"给用户分配多个roles成功")
}
router/route.go中增加路由
authGroup.POST("/AddRoleUser",handler.AddRoleUser)
authGroup.POST("/AddRolesUser",handler.AddRolesUser)
- 验证RBAC
查看数据库,就有了相对应的一条记录
-
给
角色member
分配测试uri的权限查看数据库已有相对应的记录
分别请求/api/v1/auth/testPolicy/user1
和/api/v1/auth/testPolicy/user2
user1验证未通过
user2通过
【关注我】持续更新ing。。。。。。