跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Go / Golang

Go 项目中使用 Casbin 实现 RBAC 权限管理

Go 项目集成 Casbin 库实现 RBAC 权限控制。通过配置访问控制模型定义规则,利用 GORM 适配器将策略存储于 MySQL。核心流程包括 Enforcer 初始化、中间件权限校验、策略增删改查及事务支持。涵盖用户角色分配、菜单权限管理及路径匹配优化等场景,确保权限检查高效准确。

活在当下发布于 2026/2/16更新于 2026/10/786 浏览

Go 项目中使用 Casbin 实现 RBAC 权限管理

前言

在构建企业级 Go 管理后台系统时,权限管理是一个核心功能。Casbin 是一个强大的、开源的访问控制库,支持多种访问控制模型(ACL、RBAC、ABAC 等)。本文将详细介绍如何在 Go 项目中使用 Casbin 实现完整的 RBAC(基于角色的访问控制)权限管理系统。

什么是 Casbin?

Casbin 是一个强大的、开源的访问控制库,支持多种访问控制模型:

  • ACL (Access Control List) - 访问控制列表
  • RBAC (Role-Based Access Control) - 基于角色的访问控制
  • ABAC (Attribute-Based Access Control) - 基于属性的访问控制
  • RESTful - RESTful 风格的访问控制

Casbin 的核心思想是将访问控制模型与策略分离,通过配置文件定义访问控制模型,通过适配器(Adapter)存储策略规则。

项目依赖

首先,我们需要安装 Casbin 相关的依赖包:

go get github.com/casbin/casbin/v2 github.com/casbin/gorm-adapter/v3

Casbin 模型配置

Casbin 使用模型文件(Model)来定义访问控制规则。在项目中,我们创建了 rbac_model.conf 文件:

[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) && keyMatch3(r.obj, p.obj) && regexMatch(r.act, p.act))
配置说明
  • request_definition: 定义请求格式,r = sub, obj, act 表示请求包含主体(subject)、对象(object)和操作(action)
  • policy_definition: 定义策略格式, 表示策略包含主体、对象和操作
p = sub, obj, act
  • role_definition: 定义角色继承关系,g = _, _ 表示角色继承关系(如 g, user:1, role:1 表示用户 1 继承角色 1)
  • policy_effect: 定义策略效果,e = some(where (p.eft == allow)) 表示只要有一个策略允许就允许访问
  • matchers: 定义匹配规则
    • g(r.sub, p.sub): 检查请求主体是否继承策略主体(角色继承)
    • keyMatch3(r.obj, p.obj): 使用 keyMatch3 函数匹配路径(支持 * 通配符)
    • regexMatch(r.act, p.act): 使用正则表达式匹配 HTTP 方法
  • Casbin 初始化

    在项目中,我们封装了一个 Casbin 工具包 internal/pkg/utils/casbin/casbin.go:

    package casbinx
    
    import (
    	"fmt"
    	"sync"
    
    	"github.com/casbin/casbin/v2"
    	"github.com/casbin/casbin/v2/model"
    	gormadapter "github.com/casbin/gorm-adapter/v3"
    	"gorm.io/gorm"
    )
    
    type CasbinEnforcer struct {
    	*casbin.Enforcer
    	errInit error
    	tx      *gorm.DB
    	model   model.Model
    }
    
    var casbinx = &CasbinEnforcer{}
    
    // InitEnforcer 初始化 Casbin Enforcer(仅执行一次)
    func InitEnforcer() error {
    	once.Do(func() {
    		// 1. 加载模型文件
    		modelPath, err := getModelPath()
    		if err != nil {
    			casbinx.errInit = err
    			return
    		}
    		m, err := model.NewModelFromFile(modelPath)
    		if err != nil {
    			casbinx.errInit = fmt.Errorf("加载模型失败:%w", err)
    			return
    		}
    		casbinx.model = m
    		// 2. 创建 GORM 适配器
    		db := data.MysqlDB()
    		gormadapter.TurnOffAutoMigrate(db)
    		adapter, err := gormadapter.NewAdapterByDB(db)
    		if err != nil {
    			casbinx.errInit = fmt.Errorf("创建适配器失败:%w", err)
    			return
    		}
    		// 3. 创建 Enforcer
    		enforcer, err := casbin.NewEnforcer(m, adapter)
    		if err != nil {
    			casbinx.errInit = fmt.Errorf("创建 Enforcer 失败:%w", err)
    			return
    		}
    		// 4. 启用自动保存
    		enforcer.EnableAutoSave(true)
    		casbinx.Enforcer = enforcer
    	})
    	return casbinx.errInit
    }
    
    // GetEnforcer 返回已初始化的 Enforcer 实例
    func GetEnforcer() *CasbinEnforcer {
    	if casbinx.Enforcer == nil {
    		if err := InitEnforcer(); err != nil {
    			return nil
    		}
    	}
    	return casbinx
    }
    
    关键点说明
    1. 单例模式: 使用 sync.Once 确保 Enforcer 只初始化一次
    2. GORM 适配器: 使用 gorm-adapter 将策略存储在 MySQL 数据库中
    3. 自动保存: EnableAutoSave(true) 确保策略变更后自动保存到数据库
    4. 模型加载: 从配置文件加载访问控制模型

    权限检查中间件

    在 Gin 框架中,我们通过中间件来实现权限检查:

    // internal/middleware/admin_auth.go
    func AdminAuthHandler() gin.HandlerFunc {
    	return func(c *gin.Context) {
    		// 1. 获取用户信息
    		uid := c.GetUint("uid")
    		if uid == 0 {
    			response.Fail(c, e.NotLogin, "请先登录")
    			c.Abort()
    			return
    		}
    		adminUser := getUserFromContext(c)
    		if adminUser == nil {
    			response.Fail(c, e.NotLogin, "登录已失效,请重新登录")
    			c.Abort()
    			return
    		}
    		// 2. 超级管理员跳过权限检查
    		if !isSuperAdmin(adminUser) {
    			// 3. 检查接口权限
    			if err := checkPermission(c, adminUser); err != nil {
    				if businessErr, ok := err.(*e.BusinessError); ok {
    					response.Fail(c, businessErr.GetCode(), businessErr.GetMessage())
    				} else {
    					response.Fail(c, e.ServerErr, "权限验证失败")
    				}
    				c.Abort()
    				return
    			}
    		}
    		c.Next()
    	}
    }
    
    // checkPermission 检查接口权限
    func checkPermission(c *gin.Context, adminUser *model.AdminUser) error {
    	enforcer := casbinx.GetEnforcer()
    	if enforcer.Error() != nil {
    		log.Logger.Error("权限验证初始化失败", zap.Error(enforcer.Error()))
    		return e.NewBusinessError(e.ServerErr, "权限验证初始化失败")
    	}
    	// 构建权限检查的 key
    	userKey := fmt.Sprintf("%s%s%d", global.CasbinAdminUserPrefix, global.CasbinSeparator, adminUser.ID)
    	path := c.Request.URL.Path
    	method := c.Request.Method
    	// 检查权限
    	ok, err := enforcer.Enforce(userKey, path, method)
    	if err != nil {
    		log.Logger.Error("权限验证失败", zap.Error(err))
    		return e.NewBusinessError(e.ServerErr, "权限验证失败")
    	}
    	// 如果没有权限,检查接口是否需要授权
    	if !ok {
    		if model.NewApi().CheckoutRouteIsAuth(path, method) {
    			return e.NewBusinessError(e.AuthorizationErr, "暂无接口操作权限")
    		}
    	}
    	return nil
    }
    
    权限检查流程
    1. 获取用户信息: 从 JWT Token 中解析用户 ID
    2. 构建用户标识: 使用 adminUser:1 格式标识用户(1 为用户 ID)
    3. 调用 Enforce: 使用 enforcer.Enforce(userKey, path, method) 检查权限
    4. 处理结果: 如果没有权限且接口需要授权,返回权限不足错误

    策略管理

    策略存储格式

    在数据库中,策略存储在 casbin_rule 表中,表结构如下:

    CREATE TABLE `casbin_rule` (
      `id` bigint unsigned NOT NULL AUTO_INCREMENT,
      `ptype` varchar(100) DEFAULT NULL COMMENT '策略类型:p(策略) 或 g(角色继承)',
      `v0` varchar(100) DEFAULT NULL COMMENT '主体(subject)',
      `v1` varchar(100) DEFAULT NULL COMMENT '对象(object)',
      `v2` varchar(100) DEFAULT NULL COMMENT '操作(action)',
      `v3` varchar(100) DEFAULT NULL,
      `v4` varchar(100) DEFAULT NULL,
      `v5` varchar(100) DEFAULT NULL,
      PRIMARY KEY (`id`),
      UNIQUE KEY `idx_casbin_rule` (`ptype`,`v0`,`v1`,`v2`,`v3`,`v4`,`v5`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
    
    策略类型
    P 策略(权限策略)

    格式:[p, subject, object, action]

    示例:

    • [p, menu:1, /api/v1/user/list, GET] - 菜单 1 可以访问 /api/v1/user/list 的 GET 方法
    • [p, role:1, /api/v1/user/*, *] - 角色 1 可以访问所有 /api/v1/user/* 路径的所有方法
    G 策略(角色继承)

    格式:[g, user, role]

    示例:

    • [g, adminUser:1, role:1] - 用户 1 继承角色 1 的所有权限
    • [g, role:1, menu:1] - 角色 1 继承菜单 1 的所有权限
    • [g, dept:1, role:1] - 部门 1 继承角色 1 的所有权限
    编辑权限策略

    项目中提供了 EditPolicyPermissions 方法来编辑权限策略:

    // EditPolicyPermissions 编辑策略权限
    // 策略格式:[p, 菜单 ID, 接口路径, 接口方法]
    func (e *CasbinEnforcer) EditPolicyPermissions(user string, policy [][]string) error {
    	return e.WithTransaction(func(enforcer casbin.IEnforcer) error {
    		// 1. 删除用户的所有权限
    		_, err := enforcer.DeletePermissionsForUser(user)
    		if err != nil {
    			return err
    		}
    		if len(policy) == 0 {
    			return nil
    		}
    		// 2. 构建完整的策略规则
    		var policies [][]string
    		for _, p := range policy {
    			if len(p) > 0 {
    				// 策略格式:[user, route, method]
    				policies = append(policies, append([]string{user}, p...))
    			}
    		}
    		// 3. 批量添加所有策略
    		ok, err := enforcer.AddPolicies(policies)
    		if err != nil {
    			return err
    		}
    		if !ok {
    			return errors.New("添加权限失败")
    		}
    		return nil
    	})
    }
    
    编辑角色继承

    项目中提供了 EditPolicyRoles 方法来编辑角色继承关系:

    // EditPolicyRoles 编辑策略角色
    // 策略格式:[g, user, role]
    func (e *CasbinEnforcer) EditPolicyRoles(user string, policy []string) error {
    	return e.WithTransaction(func(enforcer casbin.IEnforcer) error {
    		// 1. 删除用户的所有角色
    		_, err := enforcer.DeleteRolesForUser(user)
    		if err != nil {
    			return err
    		}
    		if len(policy) == 0 {
    			return nil
    		}
    		// 2. 构建完整的角色继承规则
    		var rules [][]string
    		for _, role := range policy {
    			if role != "" {
    				// 策略格式:[user, role]
    				rules = append(rules, []string{user, role})
    			}
    		}
    		// 3. 批量添加所有角色继承关系
    		ok, err := enforcer.AddGroupingPolicies(rules)
    		if err != nil {
    			return err
    		}
    		if !ok {
    			return errors.New("添加权限失败")
    		}
    		return nil
    	})
    }
    

    实际应用场景

    菜单权限管理

    在管理后台系统中,菜单通常与 API 接口关联。当用户编辑菜单权限时,需要更新 Casbin 策略:

    // internal/service/permission/menu.go
    // UpdateMenuPermissions 更新菜单权限
    func (s *MenuService) UpdateMenuPermissions(menu *model.Menu, apiList []uint, tx ...*gorm.DB) error {
    	// 1. 查询 API 信息
    	apis := model.List(model.NewApi(), "id IN ?", []any{apiList}, model.ListOptionalParams{
    		SelectFields: []string{"id", "route", "method"},
    	})
    	// 2. 构建策略规则
    	policy := lo.Map(apis, func(api *model.Api, _ int) []string {
    		return []string{api.Route, api.Method}
    	})
    	// 3. 更新 Casbin 策略
    	menuName := fmt.Sprintf("%s%s%d", global.CasbinMenuPrefix, global.CasbinSeparator, menu.ID)
    	enforcer := casbinx.GetEnforcer()
    	if len(tx) > 0 {
    		enforcer.SetDB(tx[0])
    	}
    	return enforcer.EditPolicyPermissions(menuName, policy)
    }
    
    用户角色分配

    当为用户分配角色时,需要更新角色继承关系:

    // internal/service/permission/admin_user.go
    // EditUserRoles 编辑用户角色
    func (s *AdminUserService) EditUserRoles(uid uint, roleIds []uint, tx ...*gorm.DB) error {
    	// 1. 构建角色标识列表
    	roleList := lo.Map(roleIds, func(roleId uint, _ int) string {
    		return fmt.Sprintf("%s%s%d", global.CasbinRolePrefix, global.CasbinSeparator, roleId)
    	})
    	// 2. 更新角色继承关系
    	userName := fmt.Sprintf("%s%s%d", global.CasbinAdminUserPrefix, global.CasbinSeparator, uid)
    	enforcer := casbinx.GetEnforcer()
    	if len(tx) > 0 {
    		enforcer.SetDB(tx[0])
    	}
    	return enforcer.EditPolicyRoles(userName, roleList)
    }
    
    角色权限管理

    角色可以继承菜单的权限,也可以继承其他角色的权限:

    // internal/service/permission/role.go
    // EditRoleMenus 编辑角色菜单
    func (s *RoleService) EditRoleMenus(roleId uint, menuIds []uint, tx ...*gorm.DB) error {
    	// 1. 构建菜单标识列表
    	menuList := lo.Map(menuIds, func(menuId uint, _ int) string {
    		return fmt.Sprintf("%s%s%d", global.CasbinMenuPrefix, global.CasbinSeparator, menuId)
    	})
    	// 2. 更新角色继承关系
    	roleName := fmt.Sprintf("%s%s%d", global.CasbinRolePrefix, global.CasbinSeparator, roleId)
    	enforcer := casbinx.GetEnforcer()
    	if len(tx) > 0 {
    		enforcer.SetDB(tx[0])
    	}
    	return enforcer.EditPolicyRoles(roleName, menuList)
    }
    

    事务支持

    在实际应用中,权限更新通常需要与业务数据更新在同一个事务中。项目提供了事务支持:

    // WithTransaction 执行事务中的操作
    func (e *CasbinEnforcer) WithTransaction(fc func(e casbin.IEnforcer) error) (err error) {
    	a, ok := e.GetAdapter().(*gormadapter.Adapter)
    	if !ok {
    		return errors.New("适配器类型错误")
    	}
    	if e.tx != nil {
    		if !isInTransaction(e.tx) {
    			return errors.New("请先通过 GORM 开启事务后传入 SetDB")
    		}
    		defer func() {
    			// 操作完成后,重置适配器
    			e.SetAdapter(a.Copy())
    			e.tx = nil
    		}()
    		// 创建事务适配器
    		gormadapter.TurnOffAutoMigrate(e.tx)
    		txAdapter, err := gormadapter.NewAdapterByDB(e.tx)
    		if err != nil {
    			return err
    		}
    		e.SetAdapter(txAdapter)
    	}
    	err = fc(e.Enforcer)
    	return
    }
    

    使用示例:

    // 在 GORM 事务中使用 Casbin
    db.Transaction(func(tx *gorm.DB) error {
    	// 1. 设置事务
    	enforcer := casbinx.GetEnforcer().SetDB(tx)
    	// 2. 更新业务数据
    	// ... 业务逻辑 ...
    	// 3. 更新权限策略(在事务中)
    	menuName := fmt.Sprintf("menu:%d", menuId)
    	return enforcer.EditPolicyPermissions(menuName, policy)
    })
    

    策略重新加载

    当策略更新后,需要重新加载策略到内存中:

    // 重新加载策略
    enforcer := casbinx.GetEnforcer()
    if err := enforcer.LoadPolicy(); err != nil {
    	log.Logger.Error("重新加载策略失败", zap.Error(err))
    }
    

    注意:虽然启用了 EnableAutoSave(true),但这只保证策略保存到数据库,不会自动加载到内存。在策略更新后,需要手动调用 LoadPolicy() 重新加载。

    权限标识规范

    项目中定义了统一的权限标识前缀:

    // internal/global/auth.go
    const (
    	CasbinAdminUserPrefix = "adminUser" // 用户前缀
    	CasbinRolePrefix      = "role"      // 角色前缀
    	CasbinMenuPrefix      = "menu"      // 菜单前缀
    	CasbinDeptPrefix      = "dept"      // 部门前缀
    	CasbinSeparator       = ":"         // 分隔符
    )
    

    权限标识格式:

    • 用户:adminUser:1(用户 ID 为 1)
    • 角色:role:1(角色 ID 为 1)
    • 菜单:menu:1(菜单 ID 为 1)
    • 部门:dept:1(部门 ID 为 1)

    常见问题与解决方案

    权限更新后不生效

    问题:更新策略后,权限检查仍然使用旧策略。

    解决方案:在策略更新后,调用 LoadPolicy() 重新加载策略:

    enforcer := casbinx.GetEnforcer()
    // 更新策略
    enforcer.EditPolicyPermissions(userName, policy)
    // 重新加载策略
    enforcer.LoadPolicy()
    
    事务回滚后策略未恢复

    问题:在事务中更新策略,事务回滚后策略未恢复。

    解决方案:确保在事务提交或回滚后,重新加载策略:

    db.Transaction(func(tx *gorm.DB) error {
    	enforcer := casbinx.GetEnforcer().SetDB(tx)
    	// 更新策略
    	return enforcer.EditPolicyPermissions(userName, policy)
    })
    // 事务提交或回滚后,重新加载策略
    enforcer := casbinx.GetEnforcer()
    enforcer.LoadPolicy()
    
    路径匹配问题

    问题:使用 keyMatch3 函数时,路径匹配不符合预期。

    解决方案:

    • keyMatch3 支持 * 通配符,如 /api/v1/user/* 可以匹配 /api/v1/user/list、/api/v1/user/detail 等
    • 如果需要更精确的匹配,可以使用 keyMatch 或 regexMatch

    最佳实践

    1. 统一权限标识格式:使用统一的前缀和分隔符,便于管理和维护
    2. 事务一致性:权限更新与业务数据更新应在同一事务中
    3. 策略重新加载:策略更新后及时重新加载,确保权限检查使用最新策略
    4. 错误处理:完善的错误处理和日志记录,便于问题排查
    5. 性能优化:策略加载到内存后,权限检查速度很快,但策略更新后需要重新加载

    总结

    本文详细介绍了如何在 Go 项目中使用 Casbin 实现 RBAC 权限管理,包括:

    • Casbin 模型配置
    • Enforcer 初始化
    • 权限检查中间件
    • 策略管理(权限策略和角色继承)
    • 事务支持
    • 实际应用场景
    • 常见问题与解决方案

    基于本文的实现,你可以快速构建一个功能完整的权限管理系统。

    目录

    1. Go 项目中使用 Casbin 实现 RBAC 权限管理
    2. 前言
    3. 什么是 Casbin?
    4. 项目依赖
    5. Casbin 模型配置
    6. 配置说明
    7. Casbin 初始化
    8. 关键点说明
    9. 权限检查中间件
    10. 权限检查流程
    11. 策略管理
    12. 策略存储格式
    13. 策略类型
    14. P 策略(权限策略)
    15. G 策略(角色继承)
    16. 编辑权限策略
    17. 编辑角色继承
    18. 实际应用场景
    19. 菜单权限管理
    20. 用户角色分配
    21. 角色权限管理
    22. 事务支持
    23. 策略重新加载
    24. 权限标识规范
    25. 常见问题与解决方案
    26. 权限更新后不生效
    27. 事务回滚后策略未恢复
    28. 路径匹配问题
    29. 最佳实践
    30. 总结

    更多推荐文章

    查看全部
    • JetBrains Copilot Agent Mode + MCP 配置实战
    • WebAI2API:将网页版 AI 转换为兼容 OpenAI 协议的 API
    • 前端 Word 文档生成实战:DOCX.js 核心用法解析
    • Stable Diffusion 16 款常用插件测评与推荐
    • Ubuntu 22.04 下 libwebkit2gtk-4.1-0 安装配置指南
    • Python GUI 可视化设计工具 tkinter-helper 介绍
    • Tomcat 入门与服务器端口配置指南
    • TRAE vs Qoder vs Cursor vs GitHub Copilot:谁才是真正的“AI 工程师”?
    • 基于 FPGA 的深度强化学习框架实现超音速闭环智能流动控制实验
    • 深入理解 Roslyn:语法树与语义分析实战
    • VSCode 配置 Java 开发环境详解
    • GitHub 教育认证通过后如何领取 Copilot Pro
    • Linux 网络基础入门:协议、分层与传输流程
    • Qwen3.5-35B-A3B-AWQ-4bit 部署避坑:enforce-eager 参数误关导致 OOM 定位与修复
    • MetaLlama 大模型系列详解:架构、部署与本地运行
    • Python 实现京东商品评论爬虫
    • 使用 uv 快速创建和管理 Python 虚拟环境
    • Web-Check 本地部署与公网远程访问实战
    • HTTP 网络协议核心概念解析
    • 二分查找算法实战:7 道经典题目详解

    相关免费在线工具

    • Base64 字符串编码/解码

      将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

    • Base64 文件转换器

      将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

    • Markdown转HTML

      将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

    • HTML转Markdown

      将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online

    • JSON 压缩

      通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online

    • JSON美化和格式化

      将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online