Go 实战:基于 Gin + Casbin 构建灵活的多租户 RBAC 权限系统
在开发后台权限系统时,我们常常需要一套灵活、可扩展、易维护的权限管理机制,特别是在支持多租户、多平台、多角色的企业系统中。经过一番调研,我最终选择了 Go + Gin + Casbin 的组合,实现基于角色的访问控制(RBAC)。本文将完整介绍这套方案的设计思路、实战代码以及一些细节优化,适合有权限控制需求的开发者参考使用。
一、项目需求要构建一个健壮的权限系统,需要解决以下几个问题:
[*]支持角色的继承(如:admin 继承 user)
[*]支持 RESTful API 的路径匹配(如 /users/:id)
[*]支持 HTTP 方法级别控制(GET/POST/PUT/DELETE)
[*]支持平台模式(单租户 / 多租户 / 混合模式)
[*]公共接口不需要登录也可访问
二、Casbin 权限模型设计
Casbin 通过配置 .conf 模型文件来定义访问控制逻辑。以下是我设计的一份多平台、角色继承、REST 路径匹配的模型:
r = mod, sub, obj, act
p = mod, sub, obj, act, eft
g = *, *
e = some(where (p.eft == allow))
m = r.sub == "root" ||
(g(r.sub, p.sub) &&
g(r.mod, p.mod) &&
(r.obj == p.obj || keyMatch2(r.obj, p.obj) || keyMatch(r.obj, p.obj) || p.obj == "*") &&
(r.act == p.act || p.act == "*"))
模型说明:
[*]mod 表示平台模式(multi/single tenant)
[*]sub 表示用户角色(如 admin/user)
[*]obj 是请求路径,支持通配符或 keyMatch2 动态匹配
[*]act 是 HTTP 方法(GET/POST 等)
[*]root 用户直接绕过权限判断,拥有所有权限
三、策略规则配置(policy.csv)策略规则可采用 CSV 文件或数据库存储。以下是一个示例策略:# 角色继承
g, admin, network_admin
g, network_admin, user
g, user, *
# 平台模式继承
g, MultiTenantCrossPlatform, MultiTenantSinglePlatform
g, MultiTenantSinglePlatform, SingleTenantCrossPlatform
g, SingleTenantCrossPlatform, SingleTenantSinglePlatform
# 公共路由
p, SingleTenantSinglePlatform, *, /healths, GET, allow
p, SingleTenantSinglePlatform, *, /login, POST, allow
# 用户路由
p, MultiTenantSinglePlatform, root, /organizations/:organizationId/users, GET, allow
p, SingleTenantSinglePlatform, admin, /users, GET, allow
p, SingleTenantSinglePlatform, user, /user, GET, allow四、Go 项目中集成 Casbin1. 使用 embed 内嵌配置文件将模型与策略文件打包进二进制中,避免部署时漏传文件:package routers
import "embed"
//go:embed model.conf policy.csv
var fs embed.FS
func MustAssetString(name string) string {
data, err := fs.ReadFile(name)
if err != nil {
panic(err)
}
return string(data)
}
2. 初始化 Casbin Enforcertype Auth struct {
Enforcer *casbin.Enforcer
}
func NewAuth() *Auth {
m := model.NewModel()
if err := m.LoadModelFromText(routers.MustAssetString("model.conf")); err != nil {
logrus.Panicf("load casbin model failed: %v", err)
}
e, err := casbin.NewEnforcer(m, textadapter.NewAdapter(routers.MustAssetString("policy.csv")))
if err != nil {
logrus.Panicf("new enforcer failed: %v", err)
}
return &Auth{Enforcer: e}
}3. Gin 中间件实现权限控制func (a *Auth) HandlerFunc() gin.HandlerFunc {
return func(c *gin.Context) {
user, valid, err := a.validateByAuthorization(c)
if err != nil {
c.JSON(401, gin.H{"error": "Unauthorized"})
c.Abort()
return
}
if !valid {
user, valid, err = a.validateByCookie(c)
if err != nil {
c.JSON(401, gin.H{"error": "Unauthorized"})
c.Abort()
return
}
}
var ok bool
if valid {
ok, _ = a.checkPermission(user, c.Request.URL.Path, c.Request.Method)
} else {
ok, _ = a.checkPublicAccess(c.Request.URL.Path, c.Request.Method)
}
if ok {
c.Next()
return
}
c.JSON(401, gin.H{"error": "Permission denied"})
c.Abort()
}
}
func (a *Auth) checkPermission(user *models.User, path, method string) (bool, error) {
return a.Enforcer.Enforce(
systems.GetPlatformMode(),// 平台模式
fmt.Sprintf("%v", user.Role),
path,
method,
)
}
func (a *Auth) checkPublicAccess(path, method string) (bool, error) {
return a.Enforcer.Enforce(
systems.GetPlatformMode(),
"*",
path,
method,
)
}五、进阶优化建议动态加载策略(支持实时授权变更)通过数据库 adapter:a.Enforcer, _ = casbin.NewEnforcer(m, xormadapter.NewAdapterByDB(db))
或使用 API 添加/删除策略:a.Enforcer.AddPolicy("platform_mode", "admin", "/api/foo", "GET")
a.Enforcer.RemovePolicy("platform_mode", "admin", "/api/foo", "GET") Redis 缓存策略(性能提升)使用 casbin-redis-watcher 实现策略变更广播,让多个服务节点同步更新缓存。watcher, _ := rediswatcher.NewWatcher("127.0.0.1:6379")
enforcer.SetWatcher(watcher)支持 ABAC 模式(按属性控制)ABAC 模型更细粒度控制,例如:
r = sub, obj, act
m = r.sub.OrgId == r.obj.OrgId && r.act == p.act
六、总结使用 Gin + Casbin 构建 RBAC 系统的关键在于:
[*]合理设计模型与继承关系
[*]灵活匹配 RESTful 路径
[*]支持平台/租户模式
[*]嵌入配置提升部署体验
[*]动态策略 + 缓存优化提升性能
在实际项目中,这套组合不仅功能完整,而且易于维护,值得推荐。
后续计划
[*]支持策略在线编辑和管理界面
[*]权限变更自动广播(基于 Redis)
[*]多语言错误提示、精细化返回码
欢迎留言交流,如果你在使用 Gin + Casbin 的过程中遇到问题,或者有更好的改进方案,欢迎在评论区一起探讨!
页:
[1]