用 jwt/v5 生成和验证 JWT 需显式设置 IssuedAt、ExpiresAt 等时间字段并调用 VerifyAudience 等校验方法,因 v5 默认启用严格校验且修复了 CVE-2022-27257 等安全问题。

Go 生成和验证 JWT 不需要框架,用 github.com/golang-jwt/jwt/v5 就够了;但 v5 默认启用严格校验,不填 IssuedAt 或漏设 VerifyAudience 会直接报错——这是绝大多数人卡住的第一关。
为什么用 jwt/v5 而不是老版本?
v4 已归档,v5 是当前维护主线,修复了关键安全问题(如 CVE-2022-27257),且默认开启 VerifyAudience、VerifyIssuer、VerifyExpiry 等校验,避免开发者“默认不校验”的惯性失误。
常见错误现象:token is expired 却没设 ExpiresAt,或 key is invalid 其实是签名算法不匹配(比如用 HS256 签但验时传了 RS256)。
- v5 要求所有时间字段(
IssuedAt、ExpiresAt、NotBefore)必须是time.Time类型,不能传 int64 时间戳 - 不再支持
ParseWithClaims(..., jwt.MapClaims)直接解析;必须定义结构体或显式传入jwt.RegisteredClaims - 若服务需兼容旧 token,得手动关掉部分校验(不推荐),例如:
jwt.WithoutClaimsValidation()
生成 JWT:最小可用代码 + 必填字段
生成 token 最容易漏的是 IssuedAt 和 ExpiresAt。v5 认为没有签发时间和过期时间的 token 不合法,哪怕你只测试用。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
package main
<p>import (
"fmt"
"time"
"github.com/golang-jwt/jwt/v5"
)</p><p>func main() {
claims := jwt.RegisteredClaims{
Issuer: "my-app",
Subject: "user-123",
Audience: []string{"web", "mobile"},
ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)),
IssuedAt: jwt.NewNumericDate(time.Now()),
NotBefore: jwt.NewNumericDate(time.Now()),
}</p><pre class="brush:php;toolbar:false;">token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
signedToken, err := token.SignedString([]byte("my-secret-key"))
if err != nil {
panic(err)
}
fmt.Println(signedToken)
}
-
Audience是切片,不是字符串;传"web"会 panic,必须是[]string{"web"} -
ExpiresAt必须用jwt.NewNumericDate(...)包装,直接写time.Now().Add(...).Unix()无效 - 密钥长度建议 ≥ 32 字节,否则 HS256 强度不足;生产环境别硬编码,从环境变量读
验证 JWT:必须显式指定校验逻辑
v5 不再自动推断校验项。即使你只关心 ExpiresAt,也得明确告诉它要验什么,否则默认只做签名检查,放行已过期 token。
func validateToken(tokenStr string) error {
token, err := jwt.ParseWithClaims(
tokenStr,
&jwt.RegisteredClaims{},
func(t *jwt.Token) (interface{}, error) {
return []byte("my-secret-key"), nil
},
)
if err != nil {
return err
}
<pre class="brush:php;toolbar:false;">// ⚠️ 关键:必须手动调用校验方法,否则 claims 不生效
if !token.Valid {
return fmt.Errorf("invalid token")
}
// 若需进一步检查 audience/issuer,需 cast 后取值
claims, ok := token.Claims.(*jwt.RegisteredClaims)
if !ok {
return fmt.Errorf("failed to cast claims")
}
if !claims.VerifyAudience([]string{"web"}, true) {
return fmt.Errorf("audience mismatch")
}
return nil
}
- 不要依赖
token.Valid判断全部合法性——它只表示签名正确且未过期(如果启用了 expiry 校验) - 校验
Audience或Issuer必须调用claims.VerifyAudience(...)或claims.VerifyIssuer(...),不能只靠字段值判断 - 若 token 里没带
aud字段,但代码调了VerifyAudience,默认返回 false;可传false第二个参数跳过缺失检查(不推荐)
常见 panic 和调试技巧
遇到 panic: reflect: call of reflect.Value.Interface on zero Value,基本是 claims 结构体字段名没按 JWT 标准命名(比如把 ExpiresAt 写成 ExpireAt);或用了自定义 claims 却没实现 Valid() 方法。
- 用
jwt.ParseUnverified临时解码看原始 payload(仅调试!不用于业务逻辑) - 校验失败时,打印
err.Error()—— v5 的错误信息很具体,比如token is expired by 123 seconds或aud claim mismatch - HTTP 请求中取 token 时,注意
Authorization: Bearer xxx的空格分割,别直接拿整个 header 值去解析 - 多实例服务共用密钥时,确保所有节点时间同步(NTP),否则
ExpiresAt和NotBefore校验会因时间差失败
JWT 本身不加密,只是签名;敏感字段(如权限列表)别塞进 payload,除非你额外 AES 加密。最常被忽略的其实是时钟偏移处理和 audience 的动态匹配逻辑——这两处一出错,前端往往只报“登录失效”,后端日志却看不出原因。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










