使用 github.com/golang-jwt/jwt/v5 生成带过期时间的 token,需构造嵌入 jwt.registeredclaims 的结构体,设置 expiresat(如 jwt.newnumericdate(time.now().add(24*time.hour))),密钥转为 []byte,用 jwt.newwithclaims 和 signedstring 签发。

怎么用 github.com/golang-jwt/jwt/v5 生成带过期时间的 token
别用老版本 jwt-go(v3 或 v4),它有已知的 nil pointer dereference 和签名绕过漏洞,v5 是官方维护的替代品。生成 token 的核心是构造一个 jwt.RegisteredClaims,再用密钥签名。
常见错误:直接传字符串密钥给 jwt.SigningMethodHS256.Sign,但 v5 要求密钥类型是 func() (any, error) 或显式转成 []byte;还有人漏设 ExpiresAt,导致 token 永不过期。
- 必须设置
ExpiresAt字段,推荐用time.Now().Add(24 * time.Hour).Unix() - 密钥建议从环境变量读取,不要硬编码;使用时转成
[]byte,例如jwt.SigningKey{Key: []byte(os.Getenv("JWT_SECRET"))} - 避免用
map[string]interface{}手动拼 claims,类型不安全,v5 推荐用结构体嵌入jwt.RegisteredClaims
type Claims struct {
Username string `json:"username"`
jwt.RegisteredClaims
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, Claims{
Username: "alice",
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)),
IssuedAt: jwt.NewNumericDate(time.Now()),
Subject: "auth",
},
})
signedToken, _ := token.SignedString([]byte("my-secret"))
验证 token 时为什么总报 token is expired 却没超时
不是 token 真过期了,大概率是服务端和客户端系统时间不同步,或者验证时没传入 jwt.WithClock 自定义时钟校准。v5 默认用 time.Now() 做时间判断,没做任何容错。
另一个高频坑:用 ParseWithClaims 但传了空的 claims 结构体指针,导致解析失败后返回 nil claims + nil error,后续取 ExpiresAt panic。
立即学习“go语言免费学习笔记(深入)”;
- 验证前务必检查
err是否为jwt.ErrTokenExpired或jwt.ErrTokenNotValidYet,而不是只看 error 是否为 nil - 加 60 秒宽松窗口:用
jwt.WithExpirationRequired()和jwt.WithIssuedAt(),再配jwt.WithTimeFunc(func() time.Time { return time.Now().Add(60 * time.Second) }) - claims 参数必须是指针,且类型要和签发时一致,否则解析会静默失败
怎么安全地把 token 存进 HTTP-only Cookie 而不是 localStorage
localStorage 易受 XSS 窃取,HTTP-only Cookie 才能防前端 JS 读取。但 Go 的 http.SetCookie 默认不设 Secure 和 SameSite,线上环境等于裸奔。
常见错误:用 Set-Cookie header 手动拼字符串,结果 HttpOnly 写成 httponly(大小写敏感)或漏掉分号;还有人把 token 放在 query string 里重定向,留下日志泄露风险。
- 用
http.Cookie结构体显式设置字段,尤其是HttpOnly: true、Secure: true(仅 HTTPS)、SameSite: http.SameSiteStrictMode或http.SameSiteLaxMode - Cookie 名别用
token这种通用名,改用auth_session或带项目前缀的,降低被其他中间件误读概率 - 值必须是 signed token 本身,不要 base64 或额外加密——JWT 已签名,再套一层反而增加解析负担和出错点
http.SetCookie(w, &http.Cookie{
Name: "auth_session",
Value: signedToken,
Path: "/",
HttpOnly: true,
Secure: true,
SameSite: http.SameSiteStrictMode,
MaxAge: 24 * 3600,
})
刷新 token 时如何避免并发重复签发导致旧 token 失效太快
用户开多个标签页,同时触发 refresh 接口,后发的请求可能覆盖先发的 token,导致第一个 tab 立刻 401。这不是 JWT 本身的问题,而是业务逻辑没控制好状态同步。
最轻量解法不是上 Redis 分布式锁,而是用 token 自带的 JTI(唯一 ID)+ 服务端短期缓存(比如 5 分钟内存 map),拒绝重复 jti 的 refresh 请求。
- 签发时生成随机
jti,例如uuid.NewString(),存进RegisteredClaims.JTI - refresh 接口收到请求后,先查这个
jti是否已在缓存中存在;存在则直接返回原 token,不重签 - 缓存 key 用
jti,value 可以是空 struct,过期时间设为略长于 token 有效期(比如 25 小时),避免误判 - 别依赖客户端传来的旧 token 的
jti做校验——它可能已被篡改,必须用服务端签发时生成的那个
时间校准、cookie 安全属性、jti 幂等控制,这三处不细看文档很容易跳过去,但线上一出问题就是登录态批量失效或者 token 被盗用。










