真正的授权必须落在 handler 里,按业务字段、资源 id、角色权限逐层判断;中间件只负责身份认证(解析 token、注入用户信息),不处理“你能干啥”的权限逻辑,避免硬编码、无法细粒度控制及调试困难。

Go 语言里做 API 授权,不能只靠中间件校验 Token 就完事——它顶多确认“你是谁”,但不回答“你能干啥”。真正的授权必须落在 handler 里,按业务字段、资源 ID、角色权限逐层判断。
为什么 AuthMiddleware 里不做权限检查
中间件的职责是身份可信性:解析 Authorization Header、验证 JWT 签名与 exp/iat、把 userID 和 role 注入 r.Context()。它不该知道“删除订单”需要 order:delete 权限,更不该硬编码某个路由对应哪些权限。
- 权限逻辑分散在中间件里,改一个角色就得翻遍所有中间件注册点
- 无法做细粒度控制:比如“只能删自己创建的订单”,需查 DB 比对
order.UserID和ctx.Value("userID") - 中间件返回 403 时,前端不知道缺哪个权限,不利于调试和提示
如何在 handler 中安全提取并校验权限
Token 解析成功后,claims 已存进 context;handler 里应直接取值、构造权限键、查策略或缓存,而非重复解析 Token 或查 DB。
- 用
r.Context().Value("claims").(*CustomClaims)获取结构体,别用map[string]interface{}—— 类型丢失易 panic - 权限字段建议放在自定义 claims 结构体里,例如
Permissions []string,避免每次从 role 查映射表 - 若权限动态加载(如 RBAC),用 Redis 缓存
user:<id>:perms</id>,TTL 设为 5 分钟,避免每次请求都查数据库 - 校验失败时返回明确错误,例如
http.Error(w, "missing permission: user:read", http.StatusForbidden)
Role-based vs Resource-based:选哪种校验方式
Role-based(RBAC)适合权限相对固定、角色少的系统;Resource-based(ReBAC)适合多租户、动态资源归属的场景。Go 中两者不互斥,常混合使用。
- RBAC 示例:
if !contains(claims.Permissions, "article:publish") { ... } - ReBAC 示例:先查
article, err := getArticleByID(id),再比对article.OwnerID == claims.UserID || claims.Role == "admin" - 注意:ReBAC 的 DB 查询必须加
SELECT ... FOR UPDATE或用乐观锁,防止并发修改资源归属导致权限绕过 - 别在 handler 开头就做 ReBAC 校验——如果后续逻辑根本不需要该资源,纯属浪费查询
刷新 Token 后权限没更新?这是设计问题
Access Token 过期后调 /auth/refresh 换新 Token,但新 Token 的 claims 仍是签发时刻的快照。如果用户权限在 refresh 期间被后台修改(如被降权),新 Token 不会自动同步。
- 解决方案一:Refresh Token 本身也带权限快照,且每次 refresh 都重新查 DB 生成新 claims(轻微性能开销,但最可靠)
- 解决方案二:在关键操作前强制 re-check 权限,例如删除敏感资源前调用
checkUserPermission(ctx, "delete", resourceID),绕过 Token 缓存 - 硬编码的权限白名单(如
map[string][]string{"admin": {"*:*"}})必须热加载,否则改配置要重启服务
权限不是一次签发就终身有效的东西。哪怕 Token 还在有效期,业务 handler 里那行 if !hasPermission(claims, "payment:refund") 才是最后一道门——漏掉它,等于没设防。
大量免费API接口:立即使用
涵盖生活服务API、金融科技API、企业工商API、等相关的API接口服务。免费API接口可安全、合规地连接上下游,为数据API应用能力赋能!











