「容器管理系统」开发篇:6. 如何在项目中集成 casbin 策略授权库?

回顾

项目已开源:基于 Golang 的 容器管理系统

上节讲述了:

  • 什么是RBAC
  • RBAC 的组成
  • RBAC 之间的关系
  • RBAC 的设计模型
  • 定义 RBAC 数据表
  • 什么是二叉树?
  • 为什么要用二叉树?
  • 二叉树的基本形态、特殊类型、性质
  • 如何生成二叉树?

光有 RBAC 的模型设计和数据是无法实现完整的权限管理的,本节咱们就引入一个授权库,来管理 RBAC 验证策略

这里引入的是 casbin 授权库

什么是 casbin

一个支持如ACL, RBAC, ABAC等访问模型,可用于Golang, Java, C/C++, Node.js, Javascript, PHP, Laravel, Python, .NET (C#), Delphi, Rust, Ruby, Lua (OpenResty), Dart (Flutter)Elixir的授权库。

casbin 的优点

  • 支持多种访问控制模型

Casbin,访问控制模型是基于PERM元模型 (Policy, Effect, Request, Matchers) 压缩而成的一个CONF文件。 因此,项目授权机制的转换或升级就像修改配置一样简单。

  • 跨语言,跨平台

所有的实现共享相同的 API行为,学习一次即可到处使用

  • 灵活的策略储存方式

除了内存和文件外,Casbin策略还可以存储在许多地方。 目前Casbin已经支持了从MySQLPostgresOracleMongoDBRedisCassandraAWS S3等数十种数据库.

  • 规模政策执行

一些适配器支持过滤策略管理。这意味着Casbin 加载的策略是基于给定过滤器的存储策略的子集。当解析整个策略成为性能瓶颈时,就能在大型多租户环境中有效地执行策略。

如何使用 casbin

这里总结了几个步骤:

  1. 需要选择你要使用的访问控制模型,本文采用的是经典的 RBAC 访问控制模型
  2. 选择策略存储方式,本文采用持久化储存到 MySQL
  3. 使用策略,本文会使用三个策略方法:AddPolicy(添加策略)RemovePolicy(删除策略)Enforce(验证策略)
  4. 编写对应的中间件,用于全局复用

封装 casbin 工具包

策略配置文件,这里就不多说了,直接从官方拉取对应访问控制模型的配置文件即可

本文使用时 rbac_model.conf
这里用到了两个三方库:

shell 复制代码
go get github.com/casbin/casbin/v2
go get github.com/casbin/gorm-adapter/v3
  • 定义 policy,编写主逻辑:
go 复制代码
package policy

import (
    "github.com/casbin/casbin/v2"
    gormadapter "github.com/casbin/gorm-adapter/v3"
    "gorm.io/gorm"
    "os"
    "path/filepath"
    "sync"
)

const (
    DIR_PATH      = "tools/casbin"  // 定义包空间路径
    CONF_PATH     = "conf"          // 定义策略配置文件目录
    RBAC_DEFAULT  = "rbac_default"  // 定义策略配置文件名
    RBAC_DOMAINS  = "rbac_domains"
    RBAC_RESOURCE = "rbac_resource_roles"
)

type CasbinService struct {
    Type      string   // 规则类型
    DB        *gorm.DB // 数据库句柄
    Prefix    string   // 自定义表前缀
    TableName string   // 自定义表名
}

var confMap map[string]string   // 通过 map 记录获取的策略配置文件
 
// 持久化到数据库

var (
    syncecEnforcer *casbin.SyncedEnforcer  // 定义全局casbin策略句柄
    once           sync.Once 
)

// init 自动初始化
func (c *CasbinService) Init() {
    pwd, _ := os.Getwd()  // 获取当前目录
    // 切换到指定目录下
    _ = os.Chdir(pwd + "/" + DIR_PATH)
    // 重新获取当前目录
    pwd, _ = os.Getwd()
    path := pwd + "/" + CONF_PATH
    //获取文件或目录相关信息
    fileInfoList, err := os.ReadDir(filepath.Clean(filepath.ToSlash(path)))
    if err != nil {
       panic(err)
    }
    // 将文件信息记录到 map 中
    confMap = make(map[string]string, len(fileInfoList))
    for i := range fileInfoList {
       confMap[fileInfoList[i].Name()] = fileInfoList[i].Name()
    }

    once.Do(func() {
       switch c.Type {
       case RBAC_DEFAULT:
          c.Type = RBAC_DEFAULT
       case RBAC_DOMAINS:
          c.Type = RBAC_DOMAINS
       case RBAC_RESOURCE:
          c.Type = RBAC_RESOURCE
       default:
          c.Type = RBAC_DEFAULT
       }
       // 通过现有的gorm实例和指定的表前缀和表名创建gorm适配器
       a, _ := gormadapter.NewAdapterByDBUseTableName(c.DB, c.Prefix, c.TableName)
       // policy 初始化持久化到 DB
       syncecEnforcer, _ = casbin.NewSyncedEnforcer(path+"/"+confMap[c.Type+".conf"], a)
    })

    // 从DB中 load 策略
    err = syncecEnforcer.LoadPolicy()
    if err != nil {
       panic(err)
    }
}

// Checked 策略验证 sub(角色), obj(路径), act(方法)
func (c *CasbinService) Checked(rivals ...interface{}) bool {
    ok, err := syncecEnforcer.Enforce(rivals)
    if err != nil {
       return false
    }
    return ok
}

// AddPolicy 添加策略 sub(角色), obj(路径), act(方法)
func (c *CasbinService) AddPolicy(rivals ...interface{}) bool {
    ok, err := syncecEnforcer.AddPolicy(rivals)
    if err != nil {
       return false
    }
    return ok
}

// RemovePolicy 删除策略 sub(角色), obj(路径), act(方法)
func (c *CasbinService) RemovePolicy(rivals ...interface{}) bool {
    ok, err := syncecEnforcer.RemovePolicy(rivals)
    if err != nil {
       return false
    }
    return ok
}
  • 初始化 policy
go 复制代码
package policy

import (
    "github.com/CodeLine-95/go-cloud-native/initial/store/db"
    policy "github.com/CodeLine-95/go-cloud-native/tools/casbin"
)

var e *policy.CasbinService

func Init() {
    // 初始化 policy 做持久化
    e = &policy.CasbinService{
       Type:      policy.RBAC_DEFAULT,
       DB:        db.D(),
       Prefix:    "cloud",
       TableName: "casbin",
    }
    e.Init()
}

// 获取初始化后的句柄
func Casbin() *policy.CasbinService {
    return e
}
  • policy 中间件
go 复制代码
package middleware

import (
    "github.com/CodeLine-95/go-cloud-native/initial/store/db"
    "github.com/CodeLine-95/go-cloud-native/initial/store/policy"
    "github.com/CodeLine-95/go-cloud-native/internal/app/constant"
    "github.com/CodeLine-95/go-cloud-native/internal/app/models"
    "github.com/CodeLine-95/go-cloud-native/internal/pkg/base"
    "github.com/CodeLine-95/go-cloud-native/internal/pkg/jwt"
    "github.com/CodeLine-95/go-cloud-native/internal/pkg/response"
    "github.com/gin-gonic/gin"
    "net/http"
)

func Policy() gin.HandlerFunc {
    return func(c *gin.Context) {
       // 角色
       // 获取 token
       token := jwt.GetToken(c.Request, "")
       // 验证token非空
       if token == "" {
          response.Error(c, http.StatusOK, nil, constant.ErrorMsg[constant.ErrorNotLogin])
          return
       }
       // token验证是否失效
       auth := token.Decode(base.JwtSignKey, false)
       if auth == nil {
          response.Error(c, http.StatusOK, nil, constant.ErrorMsg[constant.ErrorNotLogin])
          return
       }

       //获取路径
       obj := c.Request.URL.Path
       // 获取方法
       act := c.Request.Method
       // 角色
       var userRole models.CloudUserRole
       err := db.D().Where("user_id = ?", auth.UID).Find(&userRole).Error
       if err != nil {
          response.Error(c, http.StatusOK, nil, constant.ErrorMsg[constant.ErrorNotLogin])
          return
       }
       sub := userRole.RoleId

       // 验证当前访问策略
       if ok := policy.Casbin().Checked(sub, obj, act); !ok {
          response.Error(c, http.StatusOK, nil, constant.ErrorMsg[constant.ErrorNotLogin])
          return
       }
    }
}

结束语

  • 什么是 casbin
  • casbin 的优点
  • 如何使用 casbin
  • 封装 casbin 工具包
  • 如何初始化?
  • 如何编写中间件?

本节只讲了如何进行规则策略的验证,添加策略删除策略殊途同归,这里就不再贴出代码了。大家可以下去自己试一下。

相关推荐
小蒜学长2 小时前
springboot基于javaweb的小零食销售系统的设计与实现(代码+数据库+LW)
java·开发语言·数据库·spring boot·后端
brzhang2 小时前
为什么 OpenAI 不让 LLM 生成 UI?深度解析 OpenAI Apps SDK 背后的新一代交互范式
前端·后端·架构
EnCi Zheng3 小时前
JPA 连接 PostgreSQL 数据库完全指南
java·数据库·spring boot·后端·postgresql
brzhang3 小时前
OpenAI Apps SDK ,一个好的 App,不是让用户知道它该怎么用,而是让用户自然地知道自己在做什么。
前端·后端·架构
LucianaiB3 小时前
从玩具到工业:基于 CodeBuddy code CLI 构建电力变压器绕组短路智能诊断系统
后端
武子康4 小时前
大数据-118 - Flink 批处理 DataSet API 全面解析:应用场景、代码示例与优化机制
大数据·后端·flink
不要再敲了4 小时前
Spring Security 完整使用指南
java·后端·spring
IT_陈寒5 小时前
Redis 高性能缓存设计:7个核心优化策略让你的QPS提升300%
前端·人工智能·后端
brzhang5 小时前
高通把Arduino买了,你的“小破板”要变“AI核弹”了?
前端·后端·架构
程序猿阿越6 小时前
Kafka源码(六)消费者消费
java·后端·源码阅读