jwt.NewWithClaims必须传*jwt.SigningMethodHS256指针,自定义Claims需嵌入jwt.RegisteredClaims,exp/iat须为int64秒级时间戳,keyFunc必须返回[]byte,密钥建议≥32字节并缓存复用。

jwt.NewWithClaims 必须传 &jwt.SigningMethodHS256
直接写 jwt.SigningMethodHS256 会导致签名失败,token.SignedString 返回空字符串或 panic。v5 版本强制要求传指针,因为内部会做类型断言校验。
- ✅ 正确:
jwt.NewWithClaims(&jwt.SigningMethodHS256, claims) - ❌ 错误:
jwt.NewWithClaims(jwt.SigningMethodHS256, claims) - 如果用结构体自定义 Claims,必须嵌入
jwt.RegisteredClaims(不是旧版jwt.StandardClaims),否则exp/iat不被识别 -
claims["exp"]和claims["iat"]必须是int64秒级时间戳,time.Now().UnixMilli()会直接导致 token 永远过期
ParseWithClaims 的 keyFunc 必须返回 []byte
jwt.ParseWithClaims 的第三个参数是 keyFunc,它不能返回 string 或硬编码字面量 —— v5 严格校验密钥类型,返回 string 会报 key is of invalid type。
- ✅ 正确:
func(token *jwt.Token) (interface{}, error) { return []byte(secretKey), nil } - ❌ 错误:
func(token *jwt.Token) (interface{}, error) { return "mykey", nil } - 密钥长度建议 ≥32 字节;
app.conf里配的jwtkey="12345678"这种 8 字节密钥,会被 HMAC 算法降级为弱安全模式 - 别在每次解析时都调
beego.AppConfig.String("jwtkey"),应提前转成[]byte缓存复用
Beego Controller 里验证 Token 的典型拦截写法
不要把 token 解析逻辑散落在每个 handler 里。统一在基类 BaseController 中封装 ParseToken,并在需要鉴权的路由前调用。
- 从
Authorization: Bearer xxx头取值,注意分割后校验前缀是否为"Bearer",空格位置、大小写都要匹配 - 解析失败时,
token.Valid是 false,但更关键的是检查err:比如Token is expired、signature is invalid、invalid claim exp都要区分处理 - 验证通过后,把用户 ID 或权限字段写进
c.Data["user_id"],后续 handler 可直接用,避免重复解析 - 别忘了设置
c.Ctx.Input.SetData("user_id", userID),方便中间件或日志追踪
为什么 token 总是提示 “Token is expired” 却没超时
这不是服务器时间不准,而是 exp 字段写错了单位或赋值时机不对。
-
exp必须是 Unix 秒级时间戳(int64),不是毫秒,也不是字符串格式的日期 - 别在生成 token 前先算好
expireAt := time.Now().Add(...).Unix(),再塞进 claims —— 这没问题;但若写成claims["exp"] = time.Now().Add(...).UnixMilli(),就永远过期 - 客户端和服务端系统时间差超过 5 分钟(默认容差),也会触发此错误;可在
jwt.Parser初始化时传jwt.WithLeeway(30 * time.Second)调整 - 用
https://jwt.io/手动粘贴 token 查看 payload,确认exp值是否符合预期(例如1760900000对应 2025-10-某日)
实际集成中最容易被忽略的点是密钥类型和时间戳单位 —— 它们不会在编译时报错,但会让 token 在运行时静默失效,排查时容易陷入“逻辑没错,怎么就是不认”的循环。


















