基于 gin+casbin 实现的 RBAC 权限管理
最编程
2024-04-03 09:13:52
...
文档说明
基于gin+casbin实现rbac的权限管理系统
参考文档:casbin官方文档 gin文档
gin和casbin的基本使用,本文就不再叙述,本文主要介绍casbin在gin中的使用及代码实现
功能说明
本文gin+casbin使用gorm作为数据存储,在casbin中存储用户与角色的关联、角色与接口的关联
模型架构
项目结构
api/ // 接口文件
role.go
conf/ // 配置文件
casbin.conf
database/ // 数据库连接
casbin.go
model/ // 模型文件
user.go
role.go
api.go
libs/
middleware.go
casbin规则配置文件
[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 = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act || isAdmin(r.sub)
isAdmin:自定义校验用户是否为超级管理员角色的函数
封装casbin handler
package database
import (
"fmt"
"github.com/casbin/casbin/v2"
gormadapter "github.com/casbin/gorm-adapter/v3"
"os"
"sync"
)
var (
once sync.Once
Casbin *casbinHandler
)
type casbinHandler struct {
syncedEnforcer *casbin.SyncedEnforcer
}
func (c *casbinHandler) init() {
once.Do(func() {
adapter, err := gormadapter.NewAdapterByDB(DB)
if err != nil {
panic(err)
}
c.syncedEnforcer, err = casbin.NewSyncedEnforcer("conf/rbac_model.conf", adapter)
if err != nil {
panic(err)
}
})
c.syncedEnforcer.AddFunction("isAdmin", func(arguments ...interface{}) (interface{}, error) {
// 获取用户名
username := arguments[0].(string)
// 检查用户名的角色是否为超级管理员
return c.syncedEnforcer.HasRoleForUser(username, "role_1")
})
err := c.syncedEnforcer.LoadPolicy()
if err != nil {
panic(err)
}
}
// Enforce 校验权限
func (c *casbinHandler) Enforce(user, uri, action string) (bool, error) {
return c.syncedEnforcer.Enforce(user, uri, action)
}
// AddPolicy 添加策略
func (c *casbinHandler) AddPolicy(roleId int, uri, method string) (bool, error) {
return c.syncedEnforcer.AddPolicy(c.MakeRoleName(roleId), uri, method)
}
// 拼接角色ID,为了防止角色与用户名冲突
func (c *casbinHandler) MakeRoleName(roleId int) string {
return fmt.Sprintf("role_%d", roleId)
}
// AddPolicies 批量添加策略
func (c *casbinHandler) AddPolicies(rules [][]string) (bool, error) {
return c.syncedEnforcer.AddPolicies(rules)
}
// DeleteRole 删除角色对应的用户和权限
func (c *casbinHandler) DeleteRole(roleId int) (bool, error) {
return c.syncedEnforcer.DeleteRole(c.MakeRoleName(roleId))
}
// DeleteRolePolicy 删除角色下的权限
func (c *casbinHandler) DeleteRolePolicy(roleId int) (bool, error) {
return c.syncedEnforcer.RemoveFilteredNamedPolicy("p", 0, c.MakeRoleName(roleId))
}
// DeleteRoleUser 删除添加用户
func (c *casbinHandler) DeleteRoleUser(roleId int) (bool, error) {
return c.syncedEnforcer.RemoveFilteredNamedGroupingPolicy("g", 1, c.MakeRoleName(roleId))
}
// AddUserRole 添加角色和用户对应关系
func (c *casbinHandler) AddUserRole(user string, roleId int) (bool, error) {
return c.syncedEnforcer.AddGroupingPolicy(user, c.MakeRoleName(roleId))
}
// AddUserRoles 批量添加角色和用户对应关联
func (c *casbinHandler) AddUserRoles(usernames []string, roleIds []int) (bool, error) {
rules := make([][]string, 0)
for _, u := range usernames {
for _, r := range roleIds {
rules = append(rules, []string{u, c.MakeRoleName(r)})
}
}
return c.syncedEnforcer.AddGroupingPolicies(rules)
}
// DeleteUserRole 删除用户的角色信息
func (c *casbinHandler) DeleteUserRole(user string) (bool, error) {
return c.syncedEnforcer.RemoveFilteredNamedGroupingPolicy("g", 0, user)
}
模型文件
gin对接casbin的增删改查大部分都写在gorm的hook里
用户模型
针对用户增删改查后的关联角色都在模型hook中实现
type TUser struct {
Model
Username string `gorm:"column:username;type:varchar(50);unique_index" json:"username" binding:"required"`
NameCn string `gorm:"column:name_cn" json:"name_cn" binding:"required"`
Enabled int `gorm:"column:enabled" json:"enabled"`
RoleIds []int `gorm:"-" json:"role_ids"`
Roles []string `gorm:"-" json:"roles"`
}
func (TUser) TableName() string {
return "t_user"
}
// AfterFind 获取用户后,获取用户的角色信息
func (t *TUser) AfterFind(tx *gorm.DB) (err error) {
if e := database.DB.Model(&TUserRole{}).Where("user_id = ?", t.Id).Pluck("role_id", &t.RoleIds).Error; e != nil {
err = fmt.Errorf("根据权限ID<%d>获取菜单ID异常: <%s>", t.Id, e.Error())
return
}
if e := database.DB.Model(&TRole{}).Where("id in ?", t.RoleIds).Pluck("name", &t.Roles).Error; e != nil {
err = fmt.Errorf("根据菜单ID<%+v>获取菜单信息异常: <%s>", t.RoleIds, e.Error())
return
}
return
}
// AfterCreate 添加用户后,创建用户与角色的关联
func (t *TUser) AfterCreate(tx *gorm.DB) (err error) {
// 添加用户后,要添加用户角色并且添加到casbin
if _, e := database.Casbin.AddUserRoles([]string{t.Username}, t.RoleIds); e != nil {
err = fmt.Errorf("关联用户和角色到casbin异常: <%s>", e.Error())
return
}
err = t.bulkCreateUserRole()
return
}
// 批量创建用户与角色的关联
func (t *TUser) bulkCreateUserRole() (err error) {
bulks := make([]*TUserRole, 0)
for _, v := range t.RoleIds {
bulks = append(bulks, &TUserRole{RoleId: v, UserId: t.Id})
}
if e := database.DB.Create(&bulks).Error; e != nil {
err = fmt.Errorf("关联用户角色异常: <%s>", e.Error())
}
return err
}
// 删除用户与角色的关联
func (t *TUser) deleteUserRole() (err error) {
if e := database.DB.Where("user_id = ?", t.Id).Delete(&TUserRole{}).Error; e != nil {
err = fmt.Errorf("删除用户角色关联异常: <%s>", e.Error())
}
return
}
// BeforeUpdate 更新用户信息前,先清除用户角色关联,然后再重新添加
func (t *TUser) BeforeUpdate(tx *gorm.DB) (err error) {
// 清除casbin用户和角色关联
if _, e := database.Casbin.DeleteUserRole(t.Username); e != nil {
err = fmt.Errorf("删除用户<%s>的casbin角色关联异常: <%s>", t.Username, e.Error())
return
}
// 添加用户和角色关联
if err = t.deleteUserRole(); err != nil {
return
}
// 重新构建casbin用户和角色
return t.AfterCreate(tx)
}
// BeforeDelete 删除用户前清除用户与角色的关联信息
func (t *TUser) BeforeDelete(tx *gorm.DB) (err error) {
if err = t.deleteUserRole(); err != nil {
return
}
return
}
角色模型
type TRole struct {
Model
Name string `gorm:"column:name;type:varchar(50);unique_index" json:"name" binding:"required"`
Description string `gorm:"column:description" json:"description"`
}
func (TRole) TableName() string {
return "t_role"
}
// BeforeDelete 添加角色前,清除角色与用户的关联
func (t *TRole) BeforeDelete(tx *gorm.DB) (err error) {
// 清除casbin用户与角色关联
if _, e := database.Casbin.DeleteRole(t.Id); e != nil {
err = fmt.Errorf("清除casbin角色权限异常: <%s>", e.Error())
}
// 清除数据库中用户与角色的关联
return t.deleteRoleUser()
}
// 删除用户与角色的关联
func (t *TRole) deleteRoleUser() (err error) {
if e := database.DB.Where("role_id = ?", t.Id).Delete(&TUserRole{}).Error; e != nil {
err = fmt.Errorf("删除用户角色关联异常: <%s>", e.Error())
}
return
}
接口模型
增、删、改、查接口后的操作,接口与所属菜单的关系,本文就不描述菜单的模型
type TApi struct {
Model
Name string `gorm:"column:name" json:"name" binding:"required"`
Uri string `gorm:"column:uri" json:"uri" binding:"required"`
Method string `gorm:"column:method" json:"method"`
MenuIds []int `gorm:"-" json:"menu_ids" binding:"required"`
Menus []string `gorm:"-" json:"menus"`
Enabled int `gorm:"column:enabled" json:"enabled"`
Select bool `gorm:"-" json:"select"`
}
func (TApi) TableName() string {
return "t_api"
}
// AfterCreate 添加接口后,创建接口与菜单的关系
func (t *TApi) AfterCreate(tx *gorm.DB) (err error) {
// 添加好权限后,添加菜单权限
err = t.bulkCreateMenuApi()
return
}
// BeforeUpdate 更新接口前,重新关联与菜单的关系
func (t *TApi) BeforeUpdate(tx *gorm.DB) (err error) {
// 先清除权限历史菜单
if err = t.deleteMenuApi(); err != nil {
return
}
// 重新构建与菜单的关系
err = t.bulkCreateMenuApi()
return
}
// BeforeDelete 删除接口前,清除与菜单的关系
func (t *TApi) BeforeDelete(tx *gorm.DB) (err error) {
err = t.deleteMenuApi()
return
}
// 批量构建接口与菜单的关系
func (t *TApi) bulkCreateMenuApi() (err error) {
menuAuths := make([]*TMenuApi, 0)
for _, v := range t.MenuIds {
menuAuths = append(menuAuths, &TMenuApi{MenuId: v, ApiId: t.Id})
}
if e := database.DB.Create(&menuAuths).Error; e != nil {
err = fmt.Errorf("关联权限菜单异常: <%s>", e.Error())
}
return err
}
// 清除接口与菜单的关系
func (t *TApi) deleteMenuApi() (err error) {
if e := database.DB.Where("api_id = ?", t.Id).Delete(&TMenuApi{}).Error; e != nil {
err = fmt.Errorf("删除权限菜单异常: <%s>", e.Error())
}
return
}
关联模型
// 角色与接口的关联模型
type TRoleApi struct {
Id int `gorm:"primary_key" json:"id"`
RoleId int `gorm:"role_id" json:"role_id"`
ApiId int `gorm:"author_id" json:"author_id"`
Uri string `gorm:"-" json:"uri"`
}
func (TRoleApi) TableName() string {
return "t_role_api"
}
// 用户与角色的关联模型
type TUserRole struct {
Id int `gorm:"primary_key" json:"id"`
UserId int `gorm:"column:user_id" json:"user_id"`
RoleId int `gorm:"column:role_id" json:"role_id"`
}
func (TUserRole) TableName() string {
return "t_user_role"
}
更新角色的权限信息
更新角色与接口的关联,同时同步到casbin
// UpdatePermission 修改角色权限信息
// 接收数据: {"menu_list": [], "author_list": []} // 里面存的是菜单ID和权限ID
// 执行逻辑: 删除角色原有的菜单和权限信息,根据选择的重新添加即可
func (h *Handler) UpdatePermission(request *gin.Context) {
l := zap.L().With(zap.String("func", "PutRole"))
params := struct {
AuthorParams
MenuIds []int `json:"menu_ids" binding:"required"`
ApiIds []int `json:"api_ids" binding:"required"`
}{}
l.Info(fmt.Sprintf("request_data: %+v, role_id: %d", params, params.RoleId))
err := request.ShouldBindJSON(¶ms)
if err != nil {
libs.HttpParamsError(request, fmt.Sprintf("参数解析异常: <%s>", err.Error()))
return
}
role, err := model.QueryRoleInfoById(params.RoleId)
if err != nil {
libs.HttpServerError(request, err.Error())
return
}
l.Info(fmt.Sprintf("1. 删除角色权限信息.............."))
// 删除角色权限信息
tx := database.DB.Begin()
if err := tx.Where("role_id = ?", params.RoleId).Delete(&model.TRoleApi{}).Error; err != nil {
tx.Rollback()
libs.HttpServerError(request, fmt.Sprintf("删除角色权限信息异常: "+err.Error()))
return
}
l.Info("2. 删除角色菜单信息...................")
// 删除角色菜单信息
if err := tx.Where("role_id = ?", params.RoleId).Delete(&model.TRoleMenu{}).Error; err != nil {
tx.Rollback()
libs.HttpServerError(request, fmt.Sprintf("删除角色菜单信息异常: "+err.Error()))
return
}
l.Info("3. 添加角色菜单信息....................")
// 添加角色菜单信息
roleMenuList := make([]model.TRoleMenu, 0)
for _, item := range params.MenuIds {
roleMenuList = append(roleMenuList, model.TRoleMenu{RoleId: params.RoleId, MenuId: item})
}
if err := tx.Create(&roleMenuList).Error; err != nil {
tx.Rollback()
libs.HttpServerError(request, fmt.Sprintf("添加角色菜单信息异常: "+err.Error()))
return
}
l.Info("4. 添加角色权限信息....................")
// 添加角色权限信息
roleAuthorList := make([]model.TRoleApi, 0)
rules := make([][]string, 0)
for v, _ := range apisToMap(params.ApiIds) {
api, _ := model.QueryApiInfoById(v)
roleAuthorList = append(roleAuthorList, model.TRoleApi{RoleId: params.RoleId, ApiId: v})
rules = append(rules, []string{database.Casbin.MakeRoleName(role.Id), api.Uri, api.Method})
}
if err := tx.Create(&roleAuthorList).Error; err != nil {
tx.Rollback()
libs.HttpServerError(request, fmt.Sprintf("添加角色权限信息异常: "+err.Error()))
return
}
// 删除casbin权限
if _, err := database.Casbin.DeleteRolePolicy(role.Id); err != nil {
tx.Rollback()
libs.HttpServerError(request, fmt.Sprintf("清除casbin角色权限异常: <%s>", err.Error()))
return
}
// 添加新的casbin权限
if ok, err := database.Casbin.AddPolicies(rules); !ok || err != nil {
tx.Rollback()
libs.HttpServerError(request, fmt.Sprintf("添加casbin角色权限异常: <%+v>", err))
return
}
tx.Commit()
request.JSON(http.StatusOK, libs.Success(nil, "ok"))
}
权限校验
casbin在中间件中的使用
// CasbinAuthor 使用casbin进行访问控制权限
func CasbinAuthor() gin.HandlerFunc {
return func(request *gin.Context) {
// 获取用户信息
user, err := getUser(request)
if err != nil {
request.JSON(http.StatusOK, ServerError(err.Error()))
request.Abort()
return
}
// 获取请求接口和方法
obj := strings.TrimRight(request.Request.URL.Path, "/")
act := request.Request.Method
// 排除不需要校验的权限
if conf.Config.ExcludeAuth[act][obj] {
request.Next()
return
}
// 循环用户角色ID,如果有一个角色拥有权限则设置为true
success, err := database.Casbin.Enforce(user.Username, obj, act)
if err != nil {
request.JSON(http.StatusOK, ServerError(err.Error()))
request.Abort()
return
}
if !success {
request.JSON(http.StatusOK, AuthorError(fmt.Sprintf("用户<%s>无访问接口<%s>的权限", user.Username, obj)))
request.Abort()
return
}
}
}
以上代码就简单实现了基本的RBAC模型的权限管理,剩下的就是对接前端,维护下用户与角色的关系,角色与接口的关系即可。
上一篇: 关键区域硬件互斥计划
下一篇: OpenGL ES -- FBO
推荐阅读
-
基于 RBAC 权限模型的架构设计
-
基于javaWeb的长安智能医疗管理系统的设计与实现 - 致谢
-
基于微信小程序的社区管理系统设计与实现(源码+论文)_v_205
-
基于 Springboot 计算机毕业设计源码 88160 的智能公园管理系统的设计与实现
-
纯干货分享 | 研发效能提升——敏捷需求篇-而敏捷需求是提升效能的方式中不可或缺的模块之一。 云智慧的敏捷教练——Iris Xu近期在公司做了一场分享,主题为「敏捷需求挖掘和组织方法,交付更高业务价值的产品」。Iris具有丰富的团队敏捷转型实施经验,完成了企业多个团队从传统模式到敏捷转型的落地和实施,积淀了很多的经验。 这次分享主要包含以下2个部分: 第一部分是用户影响地图 第二部分是事件驱动的业务分析Event driven business analysis(以下简称EDBA) 用户影响地图,是一种从业务目标到产品需求映射的需求挖掘和组织的方法。 在软件开发过程中可能会遇到一些问题,比如大家使用不同的业务语言、技术语言,造成角色间的沟通阻碍,还会导致一些问题,比如需求误解、需求传递错误等;这会直接导致产品的功能需求和要实现的业务目标不是映射关系。 但在交付期间,研发人员必须要将这些需求实现交付,他们实则并不清楚这些功能需求产生的原因是什么、要解决客户的哪些痛点。研发人员往往只是拿到了解决方案,需要把它实现,但没有和业务侧一起去思考解决方案是否正确,能否真正的帮助客户解决问题。而用户影响地图通常是能够连接业务目标和产品功能的一种手段。 我们在每次迭代里加入的假设,也就是功能需求。首先把它先实现,再逐步去验证我们每一个小目标是否已经实现,再看下一个目标要是什么。那影响地图就是在这个过程中帮我们不断地去梳理目标和功能之间的关系。 我们在软件开发中可能存在的一些问题 针对这些问题,我们如何避免?先简单介绍做敏捷转型的常规思路: 先做团队级的敏捷,首先把产品、开发、测试人员,还有一些更后端的人员比如交互运维的同学放在一起,组成一个特训团队做交付。这个团队要包含交付过程中所涉及的所有角色。 接着业务敏捷要打通整个业务环节和研发侧的一个交付。上图中可以看到在敏捷中需求是分层管理的,第一层是业务需求,在这个层级是以用户目标和业务目标作为输入进行规划,同时需要去考虑客户的诉求。业务人员通过获取到的业务需求,进一步的和团队一起将其分解为产品需求。所以业务需求其实是我们真正去发布和运营的单元,它可以被独立发布到我们的生产环境上。我们的产品需求其实就是产品的具体功能,它是我们集成和测试的对象,也就是我们最终去部署到系统上的一个基本单元。产品需求再到了我们的开发团队,映射到迭代计划会上要把它分解为相应的技术任务,包括我们平时所说的比如一些前端的开发、后端的开发、测试都是相应的技术任务。所以业务敏捷要达到的目标是需要去持续顺畅高质量的交付业务价值。 将这几个点串起来,形成金字塔结构。最上层我们会把业务目标放在整个金字塔的塔尖。这个业务目标是通过用户的目标以及北极星指标确立的。确认业务目标后再去梳理相应的业务流程,最后生产。另外产品需求包含了操作流程和业务规则,具需求交付时间、工程时间以及我们的一些质量标准的要求。 谈到用户影响的地图,在敏捷江湖上其实有一个传说,大家都有一个说法叫做敏捷需求的“任督二脉”。用户影响地图其实就是任脉,在黑客马拉松上用过的用户故事地图其实叫督脉。所以说用户影响地图是在用户故事地图之前,先帮我们去梳理出我们要做哪些东西。当我们真正识别出我们要实现的业务活动之后,用户故事地图才去梳理我们整个的业务工作流,以及每个工作流节点下所要包含的具体功能和用户故事。所以说用户影响地图需要解决的问题,我们包括以下这些: 首先是范围蔓延,我们在整张地图上,功能和对应的业务目标是要去有一个映射的。这就避免了一些在我们比如有很多干系人参与的会议上,那大家都有不同想法些立场,会提出很多需求(正确以及错误的需求)。这个时候我们会依据目标去看这些需求是否真的是会影响我们的目标。 这里提到的错误需求,比如是利益相关的人提出的、客户认为产品应该有的、某个产品经理需求分析师认为可以有的....但是这些功能在用户影响地图中匹配不到对应目标的话,就需要降低优先级或弃掉。另外,通常我们去制定解决方案的时候,会考虑较完美的实现,导致解决方案括很多的功能。这个时候关键目标至关重要,会帮助我们梳理筛选、确定优先级。 看一下用户影响到地图概貌 总共分为一个三层的结构: 第一层why,你的业务目标哪个是最重要的,为什么?涉及到的角色有哪些? 第二层how ,怎样产生影响?影响用户角色什么样的行为? (不需要去列出所有的影响,基于业务目标) 第三层what,最关键的是在梳理需求时不需一次把所有细节想全,这通常团队中经常遇到的问题。 我们用这个例子来看一下 这是一个客服中心的影响地图,业务目标是 3个月内不增加客服人数的前提下能支持1.5倍的用户数。此业务目标设定是符合 smart 原则的,specific非常的具体,miserable 是可以衡量的,action reoriented是面向活动的, real list 也是很实际的。 量化的目标会指引我们接下来的行动,梳理一个业务目标,尽量去量化,比如 :我们通过打造一条什么样的流水线,能够提高整个部署的效率,时间是原来的 1/2 。这样才是一个能量化的有意义的目标。 回到这幅图, how 层级识别出来的内容,客服角色:想要对它施加的影响,把客户引导到论坛上,帮助客户更容易的跟踪问题,更快速的去定位问题。初级用户:方论坛上找到问题。高级用户:在论坛上回答问题。通过我们这些用户角色,进行活动,完成在不增加客户客服人数的前提下支持更多的用户数量。 最后一个层级,才是我们日常接触比较多的真正的功能的特性和需求,比如引导到客户到论坛上,其实这个产品就需要有一个常见问题的论坛的链接。这个层次需要我们团队进一步地在交付,在每个迭代之前做进一步的梳理,细化成相应的用户故事。 这个是云智慧团队中,自己做的影响地图的范例,可以看下整个的层级结构。序号表示优先级。 那我们用户影响地图可以总结为:
-
在基于虚拟页面的存储管理中实现页面替换算法 java 虚拟页面存储的三种算法
-
基于微信小程序的外卖管理系统的设计与实现(论文+源代码)_kaic
-
基于 ACM32 MCU 的两轮车充电桩解决方案,实现高效安全的电池管理
-
[能源管理系统(EMS)] 基于粒子群算法的分布式能源发电机(如光伏和电池)规模优化调度研究(Matlab 代码实现
-
实现基于 Springboot 的在线学习管理平台