「容器管理系统」开发篇: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 工具包
  • 如何初始化?
  • 如何编写中间件?

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

相关推荐
杨哥带你写代码28 分钟前
足球青训俱乐部管理:Spring Boot技术驱动
java·spring boot·后端
A尘埃1 小时前
SpringBoot的数据访问
java·spring boot·后端
yang-23071 小时前
端口冲突的解决方案以及SpringBoot自动检测可用端口demo
java·spring boot·后端
Marst Code1 小时前
(Django)初步使用
后端·python·django
代码之光_19801 小时前
SpringBoot校园资料分享平台:设计与实现
java·spring boot·后端
编程老船长1 小时前
第26章 Java操作Mongodb实现数据持久化
数据库·后端·mongodb
IT果果日记2 小时前
DataX+Crontab实现多任务顺序定时同步
后端
姜学迁3 小时前
Rust-枚举
开发语言·后端·rust
爱学习的小健4 小时前
MQTT--Java整合EMQX
后端
北极小狐4 小时前
Java vs JavaScript:类型系统的艺术 - 从 Object 到 any,从静态到动态
后端