go-casbin学习

2023-11-04

casbin学习

一、背景

1. Casbin是什么

Casbin 是一个授权库,在我们希望特定用户访问特定的 对象 或实体的流程中可以使用 主题 访问类型,例如 动作 可以是 读取, 写入, 删除 或开发者设置的任何其他动作。 这是Casbin最广泛的使用,它叫做"标准" 或经典 { subject, object, action } 流程。

Casbin能够处理除标准流量以外的许多复杂的许可使用者。 可以添加 角色 (RBAC), 属性 (ABAC) 等。

2. 功能
  • 支持自定义请求的格式,默认的请求格式为{ subject, object, action }。
  • 具有访问控制模型model和策略policy两个核心概念。
  • 支持RBAC中的多层角色继承,不止主体可以有角色,资源也可以具有角色。
  • 支持内置超级用户,例如 root 或 管理员。 超级用户可以在没有明确权限的情况下做任何事情。
  • 支持规则匹配的多个内置运营商。 例如, keyMatch 可以映射 资源密钥 /fo/bar to the pattern /foo*。
3. 不支持的功能
  • 身份认证 authentication(即验证用户的用户名和密码),Casbin 只负责访问控制。应该有其他专门的组件负责身份认证,然后由 Casbin 进行访问控制,二者是相互配合的关系。
  • 管理用户列表或角色列表。
4. PERM模型

**PERM(Policy, Effect, Request, Matchers)模型很简单, 但是反映了权限的本质 – 访问控制。**在 Casbin 中, 访问控制模型被抽象为基于 PERM (Policy, Effect, Request, Matcher) 的一个文件。 因此,切换或升级项目的授权机制与修改配置一样简单。 您可以通过组合可用的模型来定制您自己的访问控制模型。 例如,您可以在一个model中结合RBAC角色和ABAC属性,并共享一组policy规则。

  • policy: 定义权限的规则

    • 策略:定义访问策略的模式。 事实上,它在策略规则文件中界定了字段的名称和顺序。
    • 例如: p={sub, obj, act} 或 p={sub, obj, act, eft}
    • 注:如果未定义eft (policy result),则策略文件中的结果字段将不会被读取, 和匹配的策略结果将默认被允许。
  • Effect: 定义组合了多个 Policy 之后的结果, allow/deny

    • 效果。它可以被理解为一种模型,在这种模型中,对匹配结果再次作出逻辑组合判断。
    • 例如: e = some (where (p.eft == allow)) 这句话意味着,如果匹配的策略结果有一些是允许的,那么最终结果为真。
    • 让我们看看另一个示例: e = some (where (p.eft == allow)) && !some(where (p.eft == deny) 此示例组合的逻辑含义是:如果有符合允许结果的策略且没有符合拒绝结果的策略, 结果是为真。 换言之,当匹配策略均为允许(没有任何否认)是为真(更简单的是,既允许又同时否认,拒绝就具有优先地位)。
  • Request: 访问请求, 也就是谁想操作什么

    • 请求。定义请求参数。 定义请求参数。基本请求是一个元组对象,至少需要主题(访问实体)、对象(访问资源) 和动作(访问方式)
    • 例如:一个请求可能长这样: r={sub,obj,act}它实际上定义了我们应该提供访问控制匹配功能的参数名称和顺序。
  • Matcher: 判断 Request 是否满足 Policy.

    • 匹配器。匹配请求和策略的规则。
    • 例如: m = r.sub == p.sub && r.act == p.act && r.obj == p.obj 这个简单和常见的匹配规则意味着如果请求的参数(访问实体,访问资源和访问方式)匹配, 如果可以在策略中找到资源和方法,那么策略结果(p.eft)便会返回。 策略的结果将保存在 p.eft 中。

二、model语法

三、适配器

  • 适配器作用

    • 在Casbin中,策略存储作为adapter(Casbin的中间件) 实现。 Casbin用户可以使用adapter从存储中加载策略规则 (aka LoadPolicy()) 或者将策略规则保存到其中 (aka SavePolicy())。 为了保持代码轻量级,我们没有把adapter代码放在主库中。
  • 适配器对应版本

四、实战

实现步骤
1. 选择权限模型
  • ACL: RESTFUL权限模型
  • 如 /res/*, /res/: id 和 HTTP 方法, 如 GET, POST, PUT, DELETE。
  • 即什么角色,可以访问什么接口
2. 创建model.config数据
3. 策略数据保存到mysql
4. 使用gin框架集成casbin
5. 编写auth中间件完成授权
直接看代码
  • settings.go

    • 模拟一些数据

      // 模拟用户
      var Users = map[string]User{
      	"1":{
      		Name:    "zhangsan",
      		Age:     "20",
      		RoleKey: admin,
      	},
      	"2":{
      		Name:    "lisi",
      		Age:     "21",
      		RoleKey: formalMember,
      	},
      	"3":{
      		Name:    "wangwu",
      		Age:     "23",
      		RoleKey: businessAdmin,
      	},
      
      	"4":{
      		Name:    "zhaoliu",
      		Age:     "24",
      		RoleKey: normalUser,
      	},
      
      	"5":{
      		Name:    "tianqi",
      		Age:     "25",
      		RoleKey: formalMember,
      	},
      }
      
      //模拟认证
      func GetIdentity(c *gin.Context) *User {
      	auth := c.Request.Header.Get("Authentication")
      	user,ok := Users[auth]
      	if ok {
      		return &user
      	}
      	return nil
      }
      
      
  • mycasbin.go

    • 定义casbin restful权限模型,将策略存在数据库

      /**
      采用模型 RESTFUL 支持路径, 如 /res/*, /res/: id 和 HTTP 方法, 如 GET, POST, PUT, DELETE。
      keyMatch2和keyMatch区别:
      keyMatch : 一个URL 路径或 * 模式下,例如 /alice_data/*
      keyMatch2:一个URL 路径或 : 模式下,例如 /alice_data/:resource
      	r : 请求。 sub主题(访问实体)、obj对象(访问资源) 和 act动作(访问方式
      	p : 策略。 sub主题(访问实体)、obj对象(访问资源) 和 act动作(访问方式
      	m:  匹配器。请求访问实体 == 策略定义的访问实体 并且 (url匹配到请求对应中访问资源 || url匹配到请求和策略中对应的访问资源 )
      		并且 (请求中的行为与策略中的行为一致 || 策略的行为*)
      	e : 效果。 满足匹配器即为true,否则为false
      */
      
      var text = `
      [request_definition]
      r = sub, obj, act
      
      [policy_definition]
      p = sub, obj, act
      
      [policy_effect]
      e = some(where (p.eft == allow))
      
      [matchers]
      m = r.sub == p.sub && (keyMatch2(r.obj, p.obj) || keyMatch(r.obj, p.obj)) && (r.act == p.act || p.act == "*")
      `
      
      // 设置casbin
      func Setup(db *gorm.DB) (*casbin.SyncedEnforcer, error) {
      	//建立数据库连接
      	//apter, err := gormadapter.NewAdapter("mysql", "root:root1234@tcp(127.0.0.1:31234)/") // Your driver and data source.
      
      	apter, err := gormadapter.NewAdapterByDB(db)
      	if err != nil {
      		return nil, err
      	}
      
      	//将casbin model转化为字符串格式
      	modelFromString, err := model.NewModelFromString(text)
      	if err != nil {
      		return nil, err
      	}
      
      	//NewSyncedEnforcer通过文件或数据库创建同步执行器。
      	nef, err := casbin.NewSyncedEnforcer(modelFromString, apter)
      	if err != nil {
      		return nil, err
      	}
      
      	//LoadPolicy从文件/数据库重新加载策略。
      	err = nef.LoadPolicy()
      	if err != nil {
      		return nil, err
      	}
      	return nef, err
      

    }
    ```

    • 此时会在数据库中初始化一张casbin_rule的表,表是空的。长这个样子

      id ptype v0 v1 v2 v3 v4 v5
    • 定义完api,然后手动创建数据

  • application.go

    • 抽象配置

      import (
      	"net/http"
      	"sort"
      	"strings"
      	"sync"
      
      	"github.com/casbin/casbin/v2"
      	"github.com/gin-gonic/gin"
      	"gorm.io/gorm"
      )
      
      var Runtime = NewApplication()
      
      type Application struct {
      	mux     sync.RWMutex
      	db      *gorm.DB
      	casbin  *casbin.SyncedEnforcer		
      }
      
      
      // SetDb 设置对应key的db
      func (e *Application) SetDb(db *gorm.DB) {
      	e.mux.Lock()
      	defer e.mux.Unlock()
      	e.db = db
      }
      
      // GetDb 获取所有map里的db数据
      func (e *Application) GetDb() *gorm.DB {
      	e.mux.Lock()
      	defer e.mux.Unlock()
      	return e.db
      }
      
      func (e *Application) SetCasbin(enforcer *casbin.SyncedEnforcer) {
      	e.mux.Lock()
      	defer e.mux.Unlock()
      	e.casbin = enforcer
      }
      
      func (e *Application) GetCasbin() *casbin.SyncedEnforcer {
      	e.mux.Lock()
      	defer e.mux.Unlock()
      	return e.casbin
      }
      
      func NewApplication() Application {
      	return Application{}
      }
      
      
  • middleware.go

    • 模拟认证授权中间件

      import (
      	"fmt"
      	"github.com/gin-gonic/gin"
      	"net/http"
      )
      
      // 定义一些角色 TODO 未来数据库存储
      const (
      	admin = "admin"
      	businessAdmin = "businessAdmin"
      	formalMember = "formalMember"
      	normalUser = "normalUser"
      )
      
      // 检查角色,鉴权
      func AuthCheckRole() gin.HandlerFunc {
      	return func(c *gin.Context) {
      		// TODO 认证 jwt
      		user := GetIdentity(c)
      		if user == nil {
      			c.JSON(http.StatusUnauthorized, gin.H{
      				"code": 401,
      				"msg": "token过期,不存在",
      			})
      			c.Abort()
      			return
      		}
      		// 鉴权 casbin
      		if user.RoleKey == admin {
      			fmt.Printf("用户:%s, 是 %s, 直接通过! \n",user.Name,user.RoleKey)
      			c.Next()
      			return
      		}
      
      		// 数据库匹配
      		res, err := Runtime.casbin.Enforce(user.RoleKey, c.Request.URL.Path, c.Request.Method)
      		if err != nil {
      			fmt.Printf("AuthCheckRole error: %s method:%s path:%s\n", err, c.Request.Method, c.Request.URL.Path)
      			c.JSON(http.StatusOK, gin.H{
      				"code": 500,
      				"msg":  err.Error(),
      			})
      			return
      		}
      
      		// 匹配成功
      		if res {
      			fmt.Printf("username :%s, isTrue: %v, role: %s method: %s path: %s \n", user.Name,res, user.RoleKey, c.Request.Method, c.Request.URL.Path)
      			c.Next()
      		} else {
      			fmt.Printf("username :%s,isTrue: %v, role: %s method: %s path: %s message: %s \n", user.Name,res, user.RoleKey, c.Request.Method, c.Request.URL.Path, "当前request无权限,请管理员确认!")
      			c.JSON(http.StatusForbidden, gin.H{
      				"code": 403,
      				"msg":  "对不起,您没有该接口访问权限,请联系管理员",
      			})
      			c.Abort()
      			return
      		}
      	}
      }
      
      
  • mycasbin_test.go

    • 写一些测试case

      package casbin
      
      import (
      	"fmt"
      	"github.com/gin-gonic/gin"
      	"gorm.io/driver/mysql"
      	"gorm.io/gorm"
      	"net/http"
      	"net/http/httptest"
      	"testing"
      )
      
      type header struct {
      	Key   string
      	Value string
      }
      
      func performRequest(r http.Handler, method, path string, headers ...header) *httptest.ResponseRecorder {
      	req := httptest.NewRequest(method, path, nil)
      	for _, h := range headers {
      		req.Header.Add(h.Key, h.Value)
      	}
      	w := httptest.NewRecorder()
      	r.ServeHTTP(w, req)
      	return w
      }
      
      func init() {
      	// 初始化db
      	db, err := gorm.Open(mysql.Open(fmt.Sprintf("root:root1234@tcp(localhost:%v)/learnk2?charset=utf8&parseTime=True&loc=Local", 31234)), &gorm.Config{})
      	if err != nil {
      		panic(fmt.Sprintf("failed to connect db, got error: %v, port: %v", err, 31234))
      	}
      	Runtime.db = db
      
      	// 初始化 casbin
      	casbin, err := Setup(db)
      	if err != nil {
      		panic(fmt.Sprintf("failed to init casbin, got error: %v", err))
      	}
      	Runtime.casbin = casbin
      }
      
      
      // 1,2,3,4,5就当是token了
      var headers =  []header{
      	{
      		Key:   "Authentication",
      		Value: "1",
      	},
      	{
      		Key:   "Authentication",
      		Value: "2",
      	},
      	{
      		Key:   "Authentication",
      		Value: "3",
      	},
      	{
      		Key:   "Authentication",
      		Value: "4",
      	},
      	{
      		Key:   "Authentication",
      		Value: "5",
      	},
      	{
      		Key:   "Authentication",
      		Value: "6",
      	},
      }
      
      
      func TestAuth(t *testing.T) {
      	router := gin.New()
      	router.Use(AuthCheckRole())
      	router.GET("/api/v1/test", func(c *gin.Context) {
      		fmt.Println("hello")
      	}) //api资源为 /api/v1/test
      
      	//做不同角色的case测试
      	for _,h := range headers {
      		w := performRequest(router, "GET", "/api/v1/test",h)
      		fmt.Println(w.Body.String())
      	}
      }
      
      
  • 手动配置数据库资源

    • 当角色为业务管理员businessAdmin,放过

    • 当角色为正式成员formalMember, 放过

    • 数据库casbin_rule表

      id ptype v0 v1 v2 v3 v4 v5
      1 p businessAdmin /api/v1/test GET
      1 p formalMember /api/v1/test GET
测试结果
  • 测试预期

    • [zhangsan] 为 [admin] 自动放过。 打印hello
    • wangwu 为 [businessAdmin(业务管理员)] 可通过。 打印hello
    • lisi 为 [formalMember(正式成员)] 可通过。 打印hello
    • zhaoliu 为 [normalUser(普通成员)] 不可通过,打印无权限访问。
    • tianqi 为 [formalMember(正式成员)] 可通过。 打印hello
    • token=6 为无权限访问,打印401
  • 执行结果

    === RUN   TestAuth
    [GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
     - using env:	export GIN_MODE=release
     - using code:	gin.SetMode(gin.ReleaseMode)
    
    [GIN-debug] GET    /api/v1/test              --> learnk2/common/casbin.TestAuth.func1 (2 handlers)
    
    用户:zhangsan, 是 admin, 直接通过! 
    hello
    
    username :lisi, isTrue: true, role: formalMember method: GET path: /api/v1/test 
    hello
    
    username :wangwu, isTrue: true, role: businessAdmin method: GET path: /api/v1/test 
    hello
    
    username :zhaoliu,isTrue: false, role: normalUser method: GET path: /api/v1/test message: 当前request无权限,请管理员确认! 
    {"code":403,"msg":"对不起,您没有该接口访问权限,请联系管理员"}
    
    username :tianqi, isTrue: true, role: formalMember method: GET path: /api/v1/test 
    hello
    
    {"code":401,"msg":"token过期,不存在"}
    --- PASS: TestAuth (0.00s)
    PASS
    
    
  • 结论

    • 整体流程下来符合预期,将其封装成中间件,拿来即用效果更好。

五、总结

  • casbin权限这一块做的挺全面,覆盖的权限模型基本上满足日常开发使用,包括RBAC,ABAC,ACL,Restful等模型。简单学习即可上手开发。
  • 熟练掌握各种模式,和casbin的api使用,在项目中可以解决权限的大部分问题。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

go-casbin学习 的相关文章

随机推荐

  • 51单片机 并行I/O端口介绍

    在51单片机中 共有32只I O引脚 分属于4个端口 P0 P3 端口的功能介绍 1 可作为并行I O输入通道 例如按键开关连接通路 图中P1端口的作用便是作为并行I O输入通道 2 可作为并行I O输出通道 例如 数码显示器 图中P2端口
  • 重写equals()方法

    1 如果想把持久类的实例放入set中 多值关联时 1对多 建议实现equals和hashcode2 想重用托管实例时 也要实现equals和hashcode 3 多个字段组合作为联合主键 必须实现equals和hashcode方法 equa
  • Pycharm安装opencv的几种办法(windows下)

    Pycharm安装opencv的几种方法 之前在默认环境中用pip安装过一次opencv 当时就是参考别人方法弄 稀里糊涂的 然后今天想在自己别的环境下 tensorflow 下安装终于弄懂了一些 暂时发现了几种安装的方法 特此记录下 方法
  • 基于XGBoost-LSTM的天然气价格预测研究(Python代码实现)

    个人主页 研学社的博客 欢迎来到本博客 博主优势 博客内容尽量做到思维缜密 逻辑清晰 为了方便读者 座右铭 行百里者 半于九十 本文目录如下 目录 1 概述 2 运行结果 3 参考文献 4 Python代码实现 1 概述 预测成本模型是开发
  • 调试最长的一帧(第八天)

    先看看总体进度 先获取所有的图形上下文 然后进行checkEvents 请求分发消息并通过takeEvents 获取交互事件 再交由GUIEventHandler处理交互事件 中间的步骤 在checkeEvents里面 消息分发函数 消息处
  • java.lang.IllegalThreadStateException异常原因解析

    同一个Thread不能重复调用start方法 要 implements Runnable 通过使用匿名对象 如new Thread new MyThread start 可多次调用 public class test public stat
  • IOS自动打包

    打包过程 xcodebuild负责将工程源文件编译成xxx app xcrun负责给xxx app 签名并 打包成xxx ipa 第一步清理 xcodebuild clean 第二步编译 xcodebuild 第三步打包 xcrun sdk
  • ECC椭圆曲线加解密原理详解(配图)

    ECC椭圆曲线加解密原理详解 配图 本文主要参照 ECC加密算法入门介绍及 ECC椭圆曲线详解 有具体实例 前言 椭圆曲线 ECC 加密原理跟RSA加解密原理比起来 可真是晦涩难懂 拜读了Kalafinaian的文章 ECC椭圆曲线详解 有
  • Docker进入容器命令

    docker exec it 容器名称 bash 输入 exit 退出容器
  • No module named 'tensorflow.contrib'

    命令行报错如下 import tensorflow contrib slim as slim ModuleNotFoundError No module named tensorflow contrib tensorflow 2 0以后没有
  • (Struts2学习篇) Struts2数据校验之一

    数据校验的意义 WEB数据收集的复杂性 客户数据输入的误操作 其他恶意攻击 struts2数据校验的方法 客户端校验和服务端校验 客户端校验是指 在HTML画面上自动生成JavaScript校验代码 在用户提交到服务器之前在客户端浏览器中进
  • jq校验复选框是否选择了

    jq校验复选框是否选择了 var arr sup input checkbox name custom brand ids checked each function i arr sup i this val if arr sup leng
  • remote: Support for password authentication was removed on August 13, 2021. Please use a personal ac

    报错 remote Support for password authentication was removed on August 13 2021 Please use a personal access token instead r
  • 安装Ubuntu20.04后时间不准

    安装Ubuntu20 04后时间不准 买了一台瘦客户机 原先是安装Windows操作系统的 后面安装Ubuntu20 04后导致时间一直有问题 不准 解决办法 1 安装 ntpdate sudo apt get install ntpdat
  • 超硬核!程序员10种副业赚钱之道,实现月收入增加20k!

    大家好 我是良许 经常有小伙伴问我说 良许 你的副业搞得那么溜 能不能给我们介绍一些可操作性的副业 让我们在让我们在工作之余能有另外一份收入 为了响应大家的需求 本文我就整理了一些我所知道的适合程序员的副业 向大家做一个分享 这些副业 每个
  • Node.js一些报错的解决方案

    安装 https blog csdn net qq 48485223 article details 122709354 报错1 查询版本 npm v npm WARN logfile could not create logs dir E
  • Json的格式规范

    解决报错 Resolved org springframework web HttpMediaTypeNotSupportedException Content type application x www form urlencoded
  • OCR常用公开数据集整理

    OCR常用的数据集 在这个代码仓库里 提供了常用的OCR检测和识别中的通用公开数据集的下载链接 并且提供了json标签转成 txt标签的代码和转换好的 txt标签 该项目的详细github地址如下 https github com zcsw
  • 通过Windows10上的VS Code打开远端Ubuntu上的项目操作步骤

    Ubuntu版本要求是16 04及以上版本 这里以16 04为例 在Ubuntu上安装OpenSSH server 执行 sudo apt get install openssh server 在Windows 10 1803 上安装Win
  • go-casbin学习

    casbin学习 一 背景 1 Casbin是什么 Casbin 是一个授权库 在我们希望特定用户访问特定的 对象 或实体的流程中可以使用 主题 访问类型 例如 动作 可以是 读取 写入 删除 或开发者设置的任何其他动作 这是Casbin最