vlambda博客
学习文章列表

bee go融合Casbin RBAC权限控制

做过很多的网站,通常一个管理类的网站,权限控制都是比较重的一块内容,之前我的做法是会维护2张表来解决这个问题
// 用户(user)type User struct { Id int `orm:"pk"`  Name        string Description string  RoleId string //1,2,3,4}// 角色(Role)type Role struct { Id int `orm:"pk"`  Name        string //usecase/plan/index,tool/index/index Description string}
然后搭配这样的一个权限组的管理界面,定义这个组有哪些路由的权限

一般也能满足需求,但是这样也会有一个问题就是权限控制不是很具体,比如我要希望用户有【 内容管理-评论管理】的【 查看】权限,但不希望他有【 删除评论】的权限,这就需要我在 代码层面去针对用户再做权限控制,扩展性和通用性不是特别好。
RBAC权限控制模型是一个比较通用的机制,这里就不多介绍,今天我们来分享一下Casbin权控框架

简单介绍一下Casbin的工作方式,访问控制模型被抽象为基于 PERM (Policy, Effect, Request, Matcher) 的一个模型。anyway,这个不重要

// casbin.toml 文中是通过字符串赋值给tenant_rbac_model_conf的方式// 把规则写入Csbin模型的[casbin]tenant_rbac_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 = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act'''
//casbin.policy 这个文件其实是存在于数据库的,这里只是展示一些,不需要hard codingp, charger, apply/work, readp, admin, apply/usecase, readp, charger, apply/totest, readg, zhaofx, chargerp, admin, apply/task, readg, baoyx, admin
casbin.toml定义规则,在 casbin.policy配置具体用户的对资源的权限。 基于这两个文件,就可以实现对 用户、资源、操作的权限控制。

我怕生讲理论会误导读者,所以我从beego+Casbin实现的角度来深入介绍。(因为我看Casbin文档之后,对各种名词介绍,方法的使用都知道了,但是写代码的时候却不知道怎么用)

bee go融合Casbin RBAC权限控制

一个请求进来,我设计的路线是这样走的

router/router.gocontroller/index.go->Casbinconfig/config.go->InitConfigmodel/policy_models.go->GetEnforcermodel/beego_adapter.go->NewAdaptercontroller/index.go->Check
路由部分之前的文章里分享过,这里就不说了,说说controller/index.go里面的func Casbin做了什么事情
// controller/index.gopackage controllers
import ( "encoding/json" "fmt" "log" "strings" "uob/models" "uob/conf" "github.com/casbin/casbin")
type IndexController struct { BaseController}// 定义权限数据结构、基于用户、角色、资源、操作的type PermissonParam struct { User string Role string Obj string Act string}// 这是我自己测试用的模型,定义了一个路由名,一个传入的等待鉴权的信息模型type Router struct { Router string Data PermissonParam}
func (c *IndexController) Casbin(){//conf.InitConfig调用的就是conf/config.go里面的一个方法,后面会把用到的文件主要部分//./conf/casbin.toml指向的就是上面提到到的conf/casbin.toml  err := conf.InitConfig("./conf/casbin.toml") if err != nil { log.Fatal(err) }  //调用model/policy_models.go里面的方法去创建casbin对象 var e = models.GetEnforcer() //接收post传入的json参数并赋值给Router模型 data := c.Ctx.Input.RequestBody router := &Router{} jsonerr := json.Unmarshal(data, router) if jsonerr != nil { fmt.Println("json.Unmarshal is err:", jsonerr.Error()) } switch strings.Trim(router.Router," ") { case "check": fmt.Printf("check\n" ) Check(c, e, router.Data)   ... default: fmt.Printf("nothing to do\n" ) }}

上面提到的config.go文件内容如下,使用了toml库文件格式加载配置项

//conf/config.gopackage conf
import ( "github.com/BurntSushi/toml")
type Config struct { Casbin casbinConfig `toml:"casbin"`}
type casbinConfig struct { TenantRbacModelConf string `toml:"tenant_rbac_model_conf"`}
var conf *Config
func GetConfig() *Config { return conf}
//toml配置规则,根据配置文件初始化结构体的值func InitConfig(confPath string) error { _, err := toml.DecodeFile(confPath, &conf) return err}

上文还提到了需要调用models/policy_models.go去创建casbin对象

package models
import ( "uob/conf" "github.com/casbin/casbin")
// 租户(tenant),定义自己需要的字段,对应的是数据库的字段type Tenant struct {  ...}
// 角色(role)type Role struct { ...}
// 用户(user)type User struct { ...}
// 资源(resource)type Resource struct { ...}
// 操作(action)type Action struct { ...}
// Policy 策略type CasbinRule struct { Id string `orm:"pk"` PType string `json:"pType"` V0 string `json:"v0"` V1 string `json:"v1"` V2 string `json:"v2"` V3 string `json:"v3"` V4 string `json:"v4"` V5 string `json:"v5"`}
var e *casbin.Enforcer
//创建casbin对象func GetEnforcer() *casbin.Enforcer { if e == nil {  //调用beego_adapter.go里面的方法,主要做的事情是加载数据库里面的配置规则  //生成类似这样的数据  // p, charger, apply/work, read // p, admin, apply/usecase, read // p, charger, apply/totest, read // g, zhaofx, charger // p, admin, apply/task, read // g, baoyx, admin a := NewAdapter()    //这里获取casbin的配置规则,也就是casbin.toml cnf := conf.GetConfig()    m := casbin.NewModel(cnf.Casbin.TenantRbacModelConf) e = casbin.NewEnforcer(m, a) } return e}

走到这里,我们捋一遍之前我设计的路线,走到哪一步了

router/router.go-------------------------donecontroller/index.go->Casbin--------------doneconfig/config.go->InitConfig-------------donemodel/policy_models.go->GetEnforcer------donemodel/beego_adapter.go->NewAdapter-------donecontroller/index.go->Check

只差最后一步,就是真正鉴权的操作了,Check里面做了啥呢,按惯例贴代码

type allInfo struct { Res bool GetAllSubjects []string GetAllObjects []string GetAllActions []string GetAllRoles []string GetPolicy [][]string}
/**检查权限curl -X POST -H "Content-Type: application/json" -d '{"router":"check","data":{"User":"zhaofx","Role":"charger","obj":"apply/totest","act":"read"}}' "http://127.0.0.1:8080/casbin"*/func Check(c *IndexController, e *casbin.Enforcer, param PermissonParam) {  //e里面有开放很多Api,可以直接调用 GetAllSubjects := e.GetAllSubjects() GetAllObjects := e.GetAllObjects() GetAllActions := e.GetAllActions() GetAllRoles := e.GetAllRoles() GetPolicy := e.GetPolicy() fmt.Println(param.User, param.Obj, param.Act) //zhaofx apply/totest read result := e.Enforce(param.User, param.Obj, param.Act)  //这里会返回一个bool c.Data["json"] = Response{0, "success", allInfo{ result, GetAllSubjects, GetAllObjects, GetAllActions, GetAllRoles, GetPolicy, }} c.ServeJSON()}

篇幅问题,设置权限的逻辑不过多介绍了,具体实现如下

/* 给角色添加资源的访问权限  curl -X POST  -H "Content-Type: application/json" -d '{"router":"AddPolicy","data":{"User":"zhaofx","Role":"charger","obj":"apply/totest","act":"read"}}' "http://127.0.0.1:8080/casbin"  这里会成一条以p开头的casbin.policy记录,含义就是这这个角色有哪个资源的操作权限*/func AddPolicy(c *IndexController, e *casbin.Enforcer, param PermissonParam) { if !e.AddPolicy(param.User, param.Obj, param.Act) { c.Data["json"] = Response{0, "success", "add policy failed!"} c.ServeJSON() } c.Data["json"] = Response{0, "success", "success"} c.ServeJSON()}
/* 把用户和角色做关联  curl -X POST  -H "Content-Type: application/json" -d '{"router":"AddRoleForUser","data":{"User":"zhaofx","Role":"charger","obj":"apply/totest","act":"read"}}' "http://127.0.0.1:8080/casbin"  这里会成一条以g开头的casbin.policy记录,含义就是这个用户属于哪个角色*/func AddRoleForUser(c *IndexController, e *casbin.Enforcer, param PermissonParam) { fmt.Println(param) if !e.AddRoleForUser(param.User, param.Role) { c.Data["json"] = Response{0, "success", "create group in tenant failed!"} c.ServeJSON() } c.Data["json"] = Response{0, "success", "success"} c.ServeJSON()}

配置界面如下

篇幅有点长,再细节的就不展开了,写这个呢,主要是觉得网上抄来抄去的破文档太多了,都是从官方文档复制出来的,不具有实践的参考,我这个是从具体落地出发分享一下,如有不清楚的可以关注发我私信,给你发源码包,应该参考价值会更大一些

欢迎扫码关注我~~