JWT 在 Go 微服务中必须显式校验 exp/iss/aud 等字段,密钥需环境变量加载,Gin 中应透传用户上下文并统一错误响应,RBAC 权限须集中管理,RefreshToken 需 Redis 黑名单机制。

Go 微服务中不做身份认证,等于把数据库的 root 密码贴在 API 网关门口。真实生产环境里,JWT 是最常用、最轻量、也最容易翻车的方案——签发密钥硬编码、exp 不校验、iss 被忽略、刷新逻辑写成无限续期,都是高频崩塌点。
用 github.com/golang-jwt/jwt/v5 签发和验证 JWT 时必须检查的字段
新版 jwt 库(v5)默认不校验 exp、iat、nbf,也不比对 iss 或 aud,全靠手动调用 VerifyClaims 显式启用。漏掉这一步,token 永远有效。
-
Token.Claims.(jwt.MapClaims)["exp"]是 int64 时间戳,不是 Unix 秒字符串;直接用time.Now().Unix()比较会出错,必须转成time.Time再比 -
jwt.WithValidTime和jwt.WithIssuedAt是验证器选项,不是签发时的配置项 -
aud字段必须在签发时写入,并在验证时用jwt.WithAudience("api")显式声明,否则会被跳过 - 密钥不能是字符串字面量,建议从环境变量读取并做非空校验:
os.Getenv("JWT_SECRET_KEY") != ""
在 Gin 中拦截未认证请求并透传用户上下文
Gin 的中间件里别用 c.Abort() 后直接 return 就完事——这样后续 handler 拿不到 context.Context 里的用户信息,权限判断只能重复解析 token。
- 解析成功后,用
c.Set("user_id", userID)存 ID,或更稳妥地用c.Request = c.Request.WithContext(context.WithValue(c.Request.Context(), "user_id", userID)) - 不要在每个 handler 里重新解析 token:一次解析,全程复用
- 如果用了
gin-contrib/cors,确保AllowHeaders包含"Authorization",否则预检请求失败,前端拿不到 401 - 错误响应统一用
c.AbortWithStatusJSON(401, gin.H{"error": "unauthorized"}),避免暴露 token 校验细节
RBAC 权限检查不该写死在 handler 里
把 if user.Role != "admin" 塞进每个接口,等于把权限逻辑散落在二十个文件里。微服务一旦拆分,角色定义和资源路径就必然不一致。
立即学习“go语言免费学习笔记(深入)”;
- 用结构体定义权限规则:
type Permission struct { Path string; Method string; Roles []string } - 启动时加载规则表(可从 YAML 文件或 DB 初始化),用
trie或前缀树加速路径匹配 - 中间件里查规则表 + 当前用户角色,匹配失败返回 403,不继续执行 handler
- 避免用
strings.Contains(c.Request.URL.Path, "/admin/")做粗粒度判断——/admin/export和/admin/export.csv可能需要不同权限
最常被忽略的是 token 刷新机制:用 RefreshToken 时,旧 token 必须进 Redis 加黑名单(带 TTL),否则只要客户端不主动登出,长期有效的 refresh token 就等于永久后门。










