zoukankan      html  css  js  c++  java
  • 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。。。。。。

  • 相关阅读:
    api1
    录像时调用MediaRecorder的start()时发生start failed: -19错误
    继承AppCompatActivity的Activity隐藏标题栏
    Android 6.0 运行时权限处理完全解析
    Android开发用过的十大框架
    Lite Your Android English
    2015最流行的Android组件、工具、框架大全
    C#调用C++函数入口点的问题 z
    C#调用C++的DLL函数另一则(delegate) z
    C#调用C++编写的DLL函数, 以及各种类型的参数传递 z
  • 原文地址:https://www.cnblogs.com/xull0651/p/15588547.html
Copyright © 2011-2022 走看看