返回列表 发布新帖
查看: 11|回复: 0

Go 实战:基于 Gin + Casbin 构建灵活的多租户 RBAC 权限系统

发表于 昨天 15:53 | 查看全部 |阅读模式

这里或许是互联网从业者的最后一片净土,随客社区期待您的加入!

您需要 登录 才可以下载或查看,没有账号?立即注册

×
在开发后台权限系统时,我们常常需要一套灵活、可扩展、易维护的权限管理机制,特别是在支持多租户、多平台、多角色的企业系统中。经过一番调研,我最终选择了 Go + Gin + Casbin 的组合,实现基于角色的访问控制(RBAC)。
本文将完整介绍这套方案的设计思路、实战代码以及一些细节优化,适合有权限控制需求的开发者参考使用。

一、项目需求要构建一个健壮的权限系统,需要解决以下几个问题:
  • 支持角色的继承(如:admin 继承 user)
  • 支持 RESTful API 的路径匹配(如 /users/:id)
  • 支持 HTTP 方法级别控制(GET/POST/PUT/DELETE)
  • 支持平台模式(单租户 / 多租户 / 混合模式)
  • 公共接口不需要登录也可访问

二、Casbin 权限模型设计
Casbin 通过配置 .conf 模型文件来定义访问控制逻辑。以下是我设计的一份多平台、角色继承、REST 路径匹配的模型:
  1. [request_definition]
  2. r = mod, sub, obj, act

  3. [policy_definition]
  4. p = mod, sub, obj, act, eft

  5. [role_definition]
  6. g = *, *

  7. [policy_effect]
  8. e = some(where (p.eft == allow))

  9. [matchers]
  10. m = r.sub == "root" ||
  11.     (g(r.sub, p.sub) &&
  12.      g(r.mod, p.mod) &&
  13.      (r.obj == p.obj || keyMatch2(r.obj, p.obj) || keyMatch(r.obj, p.obj) || p.obj == "*") &&
  14.      (r.act == p.act || p.act == "*"))
复制代码
模型说明:
  • mod 表示平台模式(multi/single tenant)
  • sub 表示用户角色(如 admin/user)
  • obj 是请求路径,支持通配符或 keyMatch2 动态匹配
  • act 是 HTTP 方法(GET/POST 等)
  • root 用户直接绕过权限判断,拥有所有权限


三、策略规则配置(policy.csv)
策略规则可采用 CSV 文件或数据库存储。以下是一个示例策略:
  1. # 角色继承
  2. g, admin, network_admin
  3. g, network_admin, user
  4. g, user, *

  5. # 平台模式继承
  6. g, MultiTenantCrossPlatform, MultiTenantSinglePlatform
  7. g, MultiTenantSinglePlatform, SingleTenantCrossPlatform
  8. g, SingleTenantCrossPlatform, SingleTenantSinglePlatform

  9. # 公共路由
  10. p, SingleTenantSinglePlatform, *, /healths, GET, allow
  11. p, SingleTenantSinglePlatform, *, /login, POST, allow

  12. # 用户路由
  13. p, MultiTenantSinglePlatform, root, /organizations/:organizationId/users, GET, allow
  14. p, SingleTenantSinglePlatform, admin, /users, GET, allow
  15. p, SingleTenantSinglePlatform, user, /user, GET, allow
复制代码
四、Go 项目中集成 Casbin1. 使用 embed 内嵌配置文件
将模型与策略文件打包进二进制中,避免部署时漏传文件:
  1. package routers

  2. import "embed"

  3. //go:embed model.conf policy.csv
  4. var fs embed.FS

  5. func MustAssetString(name string) string {
  6.     data, err := fs.ReadFile(name)
  7.     if err != nil {
  8.         panic(err)
  9.     }
  10.     return string(data)
  11. }
复制代码
2. 初始化 Casbin Enforcer
  1. type Auth struct {
  2.     Enforcer *casbin.Enforcer
  3. }

  4. func NewAuth() *Auth {
  5.     m := model.NewModel()
  6.     if err := m.LoadModelFromText(routers.MustAssetString("model.conf")); err != nil {
  7.         logrus.Panicf("load casbin model failed: %v", err)
  8.     }

  9.     e, err := casbin.NewEnforcer(m, textadapter.NewAdapter(routers.MustAssetString("policy.csv")))
  10.     if err != nil {
  11.         logrus.Panicf("new enforcer failed: %v", err)
  12.     }

  13.     return &Auth{Enforcer: e}
  14. }
复制代码
3. Gin 中间件实现权限控制
  1. func (a *Auth) HandlerFunc() gin.HandlerFunc {
  2.     return func(c *gin.Context) {
  3.         user, valid, err := a.validateByAuthorization(c)
  4.         if err != nil {
  5.             c.JSON(401, gin.H{"error": "Unauthorized"})
  6.             c.Abort()
  7.             return
  8.         }

  9.         if !valid {
  10.             user, valid, err = a.validateByCookie(c)
  11.             if err != nil {
  12.                 c.JSON(401, gin.H{"error": "Unauthorized"})
  13.                 c.Abort()
  14.                 return
  15.             }
  16.         }

  17.         var ok bool
  18.         if valid {
  19.             ok, _ = a.checkPermission(user, c.Request.URL.Path, c.Request.Method)
  20.         } else {
  21.             ok, _ = a.checkPublicAccess(c.Request.URL.Path, c.Request.Method)
  22.         }

  23.         if ok {
  24.             c.Next()
  25.             return
  26.         }

  27.         c.JSON(401, gin.H{"error": "Permission denied"})
  28.         c.Abort()
  29.     }
  30. }

  31. func (a *Auth) checkPermission(user *models.User, path, method string) (bool, error) {
  32.     return a.Enforcer.Enforce(
  33.         systems.GetPlatformMode(),  // 平台模式
  34.         fmt.Sprintf("%v", user.Role),
  35.         path,
  36.         method,
  37.     )
  38. }

  39. func (a *Auth) checkPublicAccess(path, method string) (bool, error) {
  40.     return a.Enforcer.Enforce(
  41.         systems.GetPlatformMode(),
  42.         "*",
  43.         path,
  44.         method,
  45.     )
  46. }
复制代码
五、进阶优化建议动态加载策略(支持实时授权变更)
通过数据库 adapter:
  1. a.Enforcer, _ = casbin.NewEnforcer(m, xormadapter.NewAdapterByDB(db))
复制代码
或使用 API 添加/删除策略:
  1. a.Enforcer.AddPolicy("platform_mode", "admin", "/api/foo", "GET")
  2. a.Enforcer.RemovePolicy("platform_mode", "admin", "/api/foo", "GET")
复制代码
Redis 缓存策略(性能提升)
使用 casbin-redis-watcher 实现策略变更广播,让多个服务节点同步更新缓存。
  1. watcher, _ := rediswatcher.NewWatcher("127.0.0.1:6379")
  2. enforcer.SetWatcher(watcher)
复制代码
支持 ABAC 模式(按属性控制)
ABAC 模型更细粒度控制,例如:
  1. [request_definition]
  2. r = sub, obj, act

  3. [matchers]
  4. m = r.sub.OrgId == r.obj.OrgId && r.act == p.act
复制代码

六、总结
使用 Gin + Casbin 构建 RBAC 系统的关键在于:
  • 合理设计模型与继承关系
  • 灵活匹配 RESTful 路径
  • 支持平台/租户模式
  • 嵌入配置提升部署体验
  • 动态策略 + 缓存优化提升性能

在实际项目中,这套组合不仅功能完整,而且易于维护,值得推荐。

后续计划
  • 支持策略在线编辑和管理界面
  • 权限变更自动广播(基于 Redis)
  • 多语言错误提示、精细化返回码


欢迎留言交流,如果你在使用 Gin + Casbin 的过程中遇到问题,或者有更好的改进方案,欢迎在评论区一起探讨!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Copyright © 2001-2025 Suike Tech All Rights Reserved. 随客交流社区 (备案号:津ICP备19010126号) |Processed in 0.093426 second(s), 8 queries , Gzip On, MemCached On.
关灯 在本版发帖返回顶部
快速回复 返回顶部 返回列表