fiber.jwt中间件默认仅存*jwt.token到c.locals("user"),不解析claims;需配置parsetokenfunc才能获取user_id等字段,否则类型断言失败或返回nil。

fiber.JWT 中间件默认不给你用户数据,只存原始 *jwt.Token —— 想读 user_id 或其他字段,必须配 ParseTokenFunc。
为什么 c.Locals("user") 是 nil 或类型断言失败
根本原因不是 Token 验证失败,而是 fiber.JWT 默认行为:它只把解析后的 *jwt.Token 实例塞进上下文,不自动展开 claims。你拿到的是一个“未拆封的包裹”,不是里面的内容。
- 直接
c.Locals("user").(*jwt.Token)能取到对象,但.Claims还没被 cast 成jwt.MapClaims - 若尝试
c.Locals("user").(jwt.MapClaims),必然 panic:类型不匹配 - 即使 Token 签名有效、
exp未过期,c.Locals("user")也可能是nil——说明中间件根本没执行(比如路由没挂载、请求没带Authorization: Bearer xxx)
ParseTokenFunc 怎么写才安全可靠
这个函数决定 c.Locals("user") 最终存什么。用 github.com/golang-jwt/jwt/v5 时,签名必须是 func(*jwt.Token) (interface{}, error),且返回值要能被后续逻辑直接用。
- 别写
return token.Claims, nil——token.Claims是空接口,类型不确定,断言仍可能失败 - 显式转成
jwt.MapClaims:claims, ok := token.Claims.(jwt.MapClaims),!ok就报错 - 数字类字段(如
user_id)在 JSON 解码后默认是float64,若你数据库查出来是uint64,又没统一处理,后续claims["user_id"].(string)会 panic - 生产环境建议存字符串:
"user_id": strconv.FormatUint(user.ID, 10),彻底避开数值精度和类型抖动
Cookie 存 Token 时最容易漏掉的三项配置
用 c.Cookie() 写 Token 到浏览器,光设 Value 和 Expires 不够,缺一不可:
-
HttpOnly: true—— 阻止 JS 读取,防 XSS 盗 token -
Secure: true—— 强制 HTTPS 传输(开发环境可设false,但上线必须true) -
SameSite: "Lax"或"Strict"—— 防 CSRF;别用"None",否则必须配Secure: true -
Expires必须 ≤expclaim 值,否则浏览器 Cookie 过期晚于 Token 本身,登出后 Cookie 还在,下次请求仍可能被接受
登出不是删 Cookie 就完事
JWT 本质无状态,服务端不存 Token,所以仅清客户端 Cookie 或 localStorage,无法真正作废该 Token。必须配合黑名单(或 Redis 缓存 jti + 过期时间)。
- 登录时生成
jti(UUID),存进黑名单,设置 TTL = Token 过期时间 - 每次验证 Token 前,先查黑名单:
if redis.SIsMember(ctx, "blacklist", jti).Val() { return errors.New("token revoked") } - 别依赖
c.Locals("user")做登出判断——它只是中间件临时存的,生命周期只到当前请求结束 - 前端登出后调后端接口清 Cookie + 加入黑名单,两步缺一不可
最常被忽略的点:JWT 的 exp 是 Unix 时间戳(秒级),而 Go 的 time.Now().Add(...).Unix() 返回 int64,但某些 JWT 库(尤其旧版)对 exp 类型敏感;若你手动构造 MapClaims 时传了 int 而非 int64 或 float64,解析时可能静默失败。











