应在路由分组前用r.Use()注册JWT中间件,严格校验Authorization头、签名及exp等时间字段,区分expired与invalid错误并统一返回401,解析结果存c.Set供后续使用。

如何在 Gin 中注册 JWT 认证中间件
直接用 gin.Use() 注册自定义中间件即可,但关键在于拦截时机和错误处理逻辑是否覆盖所有非公开路径。别把中间件挂到 router := gin.Default() 之后就以为万事大吉——它必须在路由分组前注册,否则 /api/v1/user 这类路径可能绕过验证。
常见错误是把 JWT 中间件写成闭包却忘了 return,导致实际没生效;或者只对 GET 做校验,漏掉 POST/PUT 等方法。
- 中间件函数签名必须是
func(*gin.Context),且需调用c.Next()或c.Abort() - 建议按路径前缀控制开关:比如
/api/**全部校验,/health、/login显式跳过 - 不要在中间件里重复解析 token 多次——解析结果应存入
c.Set("user_id", uid)供后续 handler 使用
JWT 解析失败时该返回什么状态码和响应体
401 Unauthorized 是标准选择,但实际开发中常被误用成 403 或 500。token 过期、签名无效、结构错误都属于认证失败范畴,统一走 401;而权限不足(如用户无删除权限)才用 403。
响应体要精简,避免暴露后端细节。比如不要返回 "invalid signature" 这类提示,攻击者可借此试探密钥强度。
- 过期 token:返回
{"code":401,"msg":"token expired"},不带exp字段值 - 签名错误或解析失败:统一返回
{"code":401,"msg":"invalid token"} - 缺失
Authorization: Bearer xxx头:同样 401,不要用 400 - 务必设置
c.Abort()阻止后续 handler 执行,否则可能泄露数据
如何安全地从 Gin Context 提取并验证 JWT token
别直接用 c.GetHeader("Authorization") 然后手动切字符串——Gin 提供了更稳妥的 c.Request.Header.Get("Authorization"),且需严格校验前缀为 "Bearer "(注意末尾空格),防止伪造头绕过。
JWT 库推荐用 github.com/golang-jwt/jwt/v5,v4 已归档,v5 默认启用 VerifySignature,但要注意 ParseWithClaims 的第三个参数必须传入自定义 jwt.Keyfunc,否则无法动态获取密钥(比如从 DB 查 RSA 公钥)。
- token 字符串提取后,先检查长度是否 >7("Bearer " 占 7 字节),再
strings.TrimSpace()防空格注入 - 使用
jwt.ParseWithClaims(tokenStr, &MyClaims{}, keyFunc),其中keyFunc返回*rsa.PublicKey或[]byte - 校验时显式调用
token.Valid,不能只靠 err == nil —— 某些情况(如 exp 过期)err 为 nil 但 token.Valid == false - 避免硬编码密钥:开发环境可用 HS256 +
[]byte("dev-secret"),生产务必用 RSA 或环境变量注入
为什么 Gin 的 JWT 中间件容易忽略时间戳校验
很多人只校验签名,却忘了 exp、nbf、iat 这三个标准时间字段。Gin 中间件默认不会自动拒绝过期 token,除非你在 keyFunc 后显式检查 token.Claims.(jwt.MapClaims)["exp"]。
更隐蔽的问题是服务器时间不同步:如果机器本地时间比 NTP 快 2 分钟,就会把未过期的 token 判为过期;反之则放行已过期 token。线上服务务必配置 NTP 同步。
- 用
jwt.WithValidMethods([]string{jwt.SigningMethodHS256.Name})配合jwt.WithIssuer("myapp")等选项增强校验 - 在
keyFunc中返回nil, fmt.Errorf("token expired")可提前中断,但更推荐在ParseWithClaims后统一判断claims.VerifyExpiresAt(time.Now().Add(5*time.Minute), true) - 测试时用
time.Now().Add(-10 * time.Hour)模拟过期场景,别只测“正常流程”
Gin 的 JWT 拦截器真正难的不是写几行代码,而是每个分支都要考虑“这个请求此刻是否该被放行”,尤其是 token 刷新、多签发源、跨域预检这些边缘场景,往往一漏就成漏洞入口。


















