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.confpolicy.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")
}

可通过官网的编辑器直接运行查看结果,【直达链接】

image-20211201134847206

实战项目 参考

有些代码未做抽取勿喷

  • 目录结构如下:
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(&params)

	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
}

最后启动项目

验证

image-20211119145105282

查看mysql库里数据就有了

image-20211119145133791

  • 权限验证通过后处理正常业务逻辑

增加访问入口


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会返回没有权限

image-20211122154043079

添加api/v1/auth/addPolicy权限

image-20211122154355854

然后在执行/api/v1/auth/testPolicy 这个接口已经有权限了

image-20211122154818565
  • 查询角色下的权限

    authGroup.POST("/listPolicy",handler.GetListPolicy) //获取当前用户下的所有策略
    

func GetListPolicy( c *gin.Context)  {
	params:=CasbinRequestRoleId{}
	c.ShouldBind(&params)
	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)
}

image-20211123104815687

基于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
image-20211201103357034

查看数据库,就有了相对应的一条记录

image-20211201103428792 image-20211201103618305 image-20211201103652583
  • 角色member分配测试uri的权限

    image-20211201141557464

    查看数据库已有相对应的记录

image-20211201141618925

分别请求/api/v1/auth/testPolicy/user1/api/v1/auth/testPolicy/user2

user1验证未通过

user2通过

image-20211201142218138 image-20211201142232525

【关注我】持续更新ing。。。。。。

posted @ 2021-11-22 14:45  51运维com  阅读(313)  评论(0编辑  收藏  举报