gin-jwt.authorizer不足以做路径级权限,因其仅在令牌验证后执行一次,不自动获取请求method和path,需手动解析易出错;它适合快速拒绝,细粒度授权应交由casbin等方案实现。

直接用 gin-jwt 做接口权限控制,容易卡在“角色写了但没生效”“路径匹配不上”“中间件顺序错导致跳过校验”这三类问题上。它本身只负责认证(确认身份),不处理授权(能访问什么),必须配合额外逻辑或第三方库才能真正控权。
为什么 gin-jwt.Authorizer 不足以做路径级权限?
gin-jwt 的 Authorizer 函数只在令牌验证通过后执行一次,且只接收 *gin.Context 和原始 data(比如用户结构体),无法自动感知当前请求的 method 和 path 细节——除非你手动从 c.Request 里取,但这样写死路径判断会很快失控。
常见错误现象:
- 写了
c.Request.URL.Path == "/admin",但实际路由是GET /api/v1/admin/users,字符串不匹配就放行 - 没检查
c.Request.Method,导致DELETE /users和GET /users权限混用 -
Authorizer返回true后,后续中间件或 handler 仍可能绕过权限逻辑
可行做法:
- 把路径和方法提取逻辑封装进一个统一函数,例如
getResourceAction(c *gin.Context) (string, string),返回类似"user:read"或"admin:write"这样的抽象标识 - 在
Authorizer中调用该函数,再查表或策略做判断,避免硬编码路径字符串 - 不要依赖
Authorizer做全部授权,它更适合做“快速拒绝”,细粒度控制交给后续中间件
用 Casbin 实现 RBAC 路径权限的最小可行配置
Casbin 是目前 Gin 生态中最稳妥的授权方案,它把“谁(subject)能对什么资源(object)执行什么操作(action)”拆开管理,模型与策略分离,改权限不用动代码。
关键步骤:
- 定义模型文件
rbac_model.conf,内容必须包含[request_definition]、[policy_definition]、[role_definition]和[policy_effect]四段,少一段都会 panic - 策略数据建议存 Redis 或数据库,避免重启丢失;开发期可用
casbin.NewEnforcer("rbac_model.conf", "rbac_policy.csv") - 在 JWT 中间件之后、业务 handler 之前插入 Casbin 中间件,确保
c.MustGet("currentUser")已存在且含角色信息 - Casbin 校验调用形如
e.Enforce(role, path, method),其中path应该是标准化后的资源标识(如"/api/v1/users"),不是带参数的完整 URL
示例中间件片段:
func CasbinMiddleware(e *casbin.Enforcer) gin.HandlerFunc {
return func(c *gin.Context) {
role, _ := c.Get("role")
path := c.Request.URL.Path
method := c.Request.Method
// 注意:path 需提前清洗,比如去掉 /api/v1 前缀或映射到资源名
if !e.Enforce(role, path, method) {
c.AbortWithStatusJSON(403, gin.H{"msg": "permission denied"})
return
}
c.Next()
}
}
路由分组 + 中间件组合才是生产级权限落地方式
别指望一个中间件包打天下。真实项目中,权限控制是分层的:路由前缀决定基础访问域,JWT 确认身份,Casbin 判定动作,最后业务层再做数据级过滤(比如“只能看自己创建的订单”)。
典型分组写法:
-
public := r.Group("/api/v1")—— 不挂任何权限中间件,开放接口如登录、注册、健康检查 -
user := r.Group("/api/v1/user")—— 挂JWTMiddleware(),只认证,不控角色 -
admin := r.Group("/api/v1/admin")—— 挂JWTMiddleware()+CasbinMiddleware(e),且 Casbin 策略里明确限定admin角色对/api/v1/admin/*的 full 权限
容易踩的坑:
- 中间件顺序写反:把
CasbinMiddleware放在JWTMiddleware前面,会导致c.Get("role")取不到值,直接 403 - 路由组前缀和 Casbin 策略里的 resource 不一致,比如策略写
/admin但路由是/api/v1/admin,匹配失败 - 没处理
OPTIONS预检请求,导致前端跨域时被 Casbin 拦截,应加AllowMethodAndPathPrefixSkipper()类跳过逻辑
超级管理员如何绕过 Casbin 检查?
很多团队需要 “is_super = 1” 的账号跳过所有权限规则。这个逻辑不能写在 Casbin 模型里,而应在中间件中前置判断。
正确做法:
- 在 Casbin 中间件开头,先从上下文取用户信息:
user, ok := c.Get("currentUser").(*User) - 判断
user.IsSuper == 1,如果是,直接c.Next(),不走e.Enforce() - 不要在 Casbin 策略里为 super 用户加一堆
*规则,那会污染策略数据,也难审计
注意:IsSuper 字段必须在 JWT 的 PayloadFunc 里写入 Claims,并在 JWT 中间件中解析后存入 context,否则中间件拿不到。
最易被忽略的一点:Casbin 的 Enforce 方法默认区分大小写,GET 和 get 是不同 action,策略文件里写的 method = GET 就不能匹配到小写的请求方法。务必统一转成大写再传入。











