根本原因是未指定与JWT header中alg字段严格匹配的SigningMethod,或keyFunc未校验算法合法性,导致签名验证失败或被篡改header绕过校验。

为什么直接用 jwt-go 的 Parse 会校验失败?
常见现象是:客户端传了带 signature 的 JWT,服务端调用 jwt.Parse 却返回 token is invalid 或 signature is invalid。根本原因不是密钥错了,而是没指定正确的 SigningMethod —— 比如 token 是用 HS256 签的,但你传了 jwt.SigningMethodHS512 做验证;或者更隐蔽的情况:token header 里声明的是 "alg": "HS256",但你用的解析函数没做算法白名单限制,导致被篡改 header 后绕过校验。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 始终用
jwt.ParseWithClaims(tokenString, claims, keyFunc),而非无keyFunc的简版 -
keyFunc必须返回与 header 中alg字段严格匹配的密钥,且应拒绝非预期算法(比如只允许HS256) - 在
keyFunc里加日志或 panic,打印收到的token.Header["alg"],确认是否被客户端恶意修改
如何让签名校验同时防 header 篡改和 payload 篡改?
JWT 防篡改依赖三段拼接后的整体签名:header.payload.signature。但很多开发者只校验 signature 是否有效,却忽略 header 本身也可能被替换(例如把 HS256 改成 none)。jwt-go v3 及以前默认允许 none 算法,v4+ 已禁用,但仍需手动加固。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 显式在
keyFunc中检查token.Method.Alg(),不匹配就返回nil, fmt.Errorf("alg not allowed") - 不要信任
token.Header里的任何字段用于业务逻辑(比如从 header 取kid再查密钥),应先完成签名验证再读取 - 若需支持多密钥(如轮换),
keyFunc应根据token.Header["kid"]查找对应密钥,但必须确保该kid在签名验证通过后才可信
Go Gin 框架中怎么嵌入 JWT 校验中间件?
Gin 的中间件本质是函数链,关键点在于:校验失败要立即中断请求,且不能泄露敏感错误细节给客户端。直接用 c.AbortWithStatusJSON(401, ...) 是常见写法,但容易漏掉 context 取消或 header 污染。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 中间件里调用
token, err := jwt.ParseWithClaims(...)后,必须检查err == nil && token.Valid,二者缺一不可 - 校验成功后,用
c.Set("user_id", claims.UserID)把可信字段存入 context,下游 handler 通过c.MustGet("user_id")获取 - 避免在中间件里返回完整
err.Error(),统一返回{"code": 401, "msg": "unauthorized"},防止暴露密钥长度、算法等信息
为什么生产环境要用 HS256 而不是 RS256?
这不是绝对规则,但多数内部 API 场景下,HS256 更轻量、更可控。问题常出在密钥管理上:有人把 RSA 私钥硬编码进代码,或误将公钥当私钥用,导致签名永远失败;也有人混淆了 RS256 的密钥类型(*rsa.PrivateKey vs *rsa.PublicKey),引发 panic。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 若选
HS256,密钥长度至少 32 字节(256 bit),用os.Getenv("JWT_SECRET")加载,禁止明文写死 - 若选
RS256,签名时用*rsa.PrivateKey,校验时用*rsa.PublicKey,且必须用jwt.ParseRSAPublicKeyFromPEM解析 PEM 公钥,不能直接传字符串 - 无论哪种算法,都应在启动时验证密钥有效性(如
rsa.VerifyPKCS1v15或hmac.Equal检查长度),失败则 panic,不带病运行
最易被忽略的一点:JWT 的 iat 和 exp 是 Unix 时间戳(秒级),但 Go 的 time.Now().Unix() 是秒,time.Now().UnixMilli() 是毫秒 —— 混用会导致所有 token 瞬间过期或永不过期。


















