回顾
项目已开源:基于 Golang 的 容器管理系统
- 「容器管理系统」1. 开篇:框架选型和环境搭建
- 「容器管理系统」开发篇:1. 初始化配置和日志监控
- 「容器管理系统」开发篇:2. 封装gin统一返回JSON
- 「容器管理系统」开发篇:3. JWT(JSON Web Token)的应用
- 「容器管理系统」开发篇:4. Gin 如何优雅的使用 struct 的 tag 标签
- 「容器管理系统」开发篇:5. 如何实现 RBAC 权限管理(一)
- 「容器管理系统」开发篇:5. 如何实现 RBAC 权限管理(二)
上节讲述了:
- 什么是
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
已经支持了从MySQL
、Postgres
、Oracle
到MongoDB
、Redis
、Cassandra
、AWS S3
等数十种数据库.
- 规模政策执行
一些适配器支持过滤策略管理。这意味着Casbin 加载的策略是基于给定过滤器的存储策略的子集。当解析整个策略成为性能瓶颈时,就能在大型多租户环境中有效地执行策略。
如何使用 casbin
?
这里总结了几个步骤:
- 需要选择你要使用的访问控制模型,本文采用的是经典的
RBAC 访问控制模型
- 选择策略存储方式,本文采用持久化储存到
MySQL
中 - 使用策略,本文会使用三个策略方法:
AddPolicy(添加策略)
、RemovePolicy(删除策略)
、Enforce(验证策略)
- 编写对应的中间件,用于全局复用
封装 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
工具包 - 如何初始化?
- 如何编写中间件?
本节只讲了如何进行规则策略的验证,
添加策略
和删除策略
殊途同归,这里就不再贴出代码了。大家可以下去自己试一下。