权限校验中间件需统一挂载到所有受保护路由组,从JWT或session提取角色避免查库,失败时用c.AbortWithStatusJSON(403);资源级权限须在handler中结合参数二次校验,职责分离;c.MustGet("user") panic多因中间件顺序错误或未set,应改用c.Get+ok判断。

权限校验中间件怎么写才不绕过
直接在 gin.HandlerFunc 里做角色判断是最常见也最容易出错的方式。绕过往往不是逻辑漏洞,而是没覆盖所有入口——比如忘了给静态资源路由加中间件,或者用 c.Next() 前就提前 c.Abort() 导致后续中间件失效。
- 必须对所有需要保护的路由组统一挂载中间件,别只在某个
router.POST()单独加 - 角色字段建议从 JWT payload 或 session 中提取,避免每次查 DB;但要注意 token 刷新时角色变更的同步问题
- 校验失败统一返回
c.AbortWithStatusJSON(403, gin.H{"error": "forbidden"}),别用c.JSON()+c.Abort()分两步,容易漏掉Abort() - 如果用了 Gin 的
Authz第三方包(如gin-contrib/authz),注意它默认只支持 RBAC 的静态策略,动态权限(如“编辑自己创建的文章”)得自己扩展
角色数据存在哪?DB 还是内存 Map?
小项目用 map[string][]string 存角色-权限映射够用,但上线后几乎必然要换 DB。别图省事硬编码角色权限,改个权限就得重新编译部署。
- 推荐用两张表:
roles(id, name)和role_permissions(role_id, permission_code),permission_code用类似user:read、post:delete这种字符串,别用数字 ID - 启动时可预加载到内存
sync.Map,key 是 role ID,value 是[]string权限列表;但需监听 DB 变更或设置 TTL 缓存,否则权限更新不生效 - 用户登录后把角色 ID 和权限列表一起塞进 JWT 的
claims,避免每次请求都查库;但要注意 token 过期时间别设太长,否则权限回收延迟
如何支持「资源级」权限(比如只允许编辑自己的文章)
纯角色控制解决不了这类问题,得结合路由参数和业务逻辑做二次校验。Gin 本身不提供资源级抽象,得自己补。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 先用中间件做粗粒度控制(如
post:edit),再在 handler 里取c.Param("id")查文章作者,对比当前用户 ID - 别在中间件里查具体资源——中间件不知道你要操作哪个 post,它只管“有没有 edit 权限”,资源归属判断必须落到业务 handler
- 可以封装一个通用函数,比如
checkOwner(c *gin.Context, resourceID string, userID uint) bool,但别把它塞进权限中间件,职责要分开 - 如果资源归属逻辑复杂(如团队协作场景),建议把校验逻辑下沉到 service 层,handler 只负责传参和错误返回
为什么 c.MustGet("user") 经常 panic
因为中间件执行顺序错了,或者没在前置中间件里往 context 写入 user 对象。Gin 的 c.MustGet() 在 key 不存在时直接 panic,不是返回 nil。
立即学习“go语言免费学习笔记(深入)”;
- 确保解析 token / session 的中间件(比如叫
authMiddleware)一定在权限中间件之前注册 - 写入时用
c.Set("user", user),而不是c.Set("user_id", userID)—— 后者导致权限中间件还得再去查用户信息,多一次 DB 请求 - 检查是否在某个分支里漏了
c.Set(),比如匿名用户没 set,但权限中间件仍调c.MustGet("user") - 更安全的做法是用
user, ok := c.Get("user")+if !ok { c.AbortWithStatusJSON(401, ...) }

















