fiber.jwt中间件默认不校验token过期,因其底层jwt/v3仅用signingkey验证签名,不自动检查exp字段;需显式配置expirederror、timefunc及嵌入jwt.standardclaims才能启用时间校验。

为什么 fiber.JWT 中间件默认不校验 token 过期?
因为 fiber.JWT(v2.40+)底层调用的是 github.com/gofiber/jwt/v3,而它的默认配置里 SigningKey 仅用于验证签名,不会自动检查 exp 字段。你拿到一个已过期的 token,中间件照样放行,直到你手动调用 c.Locals("user") 后解析才发现问题。
必须显式启用时间校验:
jwt.New(jwt.Config{
SigningKey: []byte("secret"),
ContextKey: "user",
TokenLookup: "header:Authorization",
AuthScheme: "Bearer",
ExpiredError: func(c *fiber.Ctx) error {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "token expired"})
},
})
-
ExpiredError是钩子函数,但只在 token 确实过期时触发——前提是jwt包能读到exp字段,所以签发时务必带上exp - 别漏掉
TimeFunc配置:默认用time.Now,若服务时钟不同步(比如容器没同步 NTP),会导致误判过期 - 如果你用的是自定义 claims 结构,要确保嵌入了
jwt.StandardClaims,否则exp不会被识别
如何安全地从 c.Locals("user") 提取用户 ID?
直接类型断言 c.Locals("user").(*jwt.Token) 是危险的——它可能为 nil(比如中间件没命中),也可能不是 *jwt.Token 类型(比如你改过配置的 ContextKey)。
正确做法是双重检查:
if user := c.Locals("user"); user != nil {
if token, ok := user.(*jwt.Token); ok && token.Valid {
if claims, ok := token.Claims.(jwt.MapClaims); ok {
if id, ok := claims["id"].(float64); ok {
userID := int64(id) // JSON number → float64 → int64
// ✅ 安全使用 userID
}
}
}
}
- 永远先判空再断言,
c.Locals("user")在未认证或解析失败时返回nil -
token.Valid必须检查,它代表签名和时间都通过(前提是前面配了ExpiredError和标准 claims) - JWT payload 中的数字默认被解析为
float64,强转int64前确认无小数位,否则会丢精度
fiber.JWT 和自定义中间件混用时为啥总 401?
常见于你写了登录路由用 jwt.New() 生成 token,又在其他路由用另一个中间件做权限判断——但两个中间件的 SigningKey、TokenLookup 或 AuthScheme 不一致,导致解析失败。
典型错误组合:
在 Go 中使用 google/wire 实现编译时依赖注入——wire.NewSet、wire.Build、wire.Bind(接口→实现)、wire.Struct、wire.Value、wire.Interface
- 登录接口返回的 token 用
Bearer xxx格式,但保护路由中间件设了AuthScheme: "Token" - 前端把 token 放在
Cookie里,但中间件只查header:Authorization - 开发环境用
"secret",生产环境密钥从 env 读,但 env 没配,降级成空字符串 → 签名验证永远失败
调试技巧:在中间件里加一行日志,打印原始 token 字符串和解析错误:
jwt.New(jwt.Config{
// ... 其他配置
ErrorHandler: func(c *fiber.Ctx, err error) error {
log.Printf("JWT error on %s: %v", c.Path(), err) // 查看具体哪步失败
return c.Status(fiber.StatusUnauthorized).SendString("unauthorized")
},
})
刷新 token 时为何新旧 token 同时有效?
Fiber 的 JWT 中间件本身不提供黑名单或存储机制,exp 是唯一过期依据。一旦签发,只要没过期,哪怕你“刷新”了,旧 token 依然能用——这是 JWT 的无状态特性决定的,不是 bug。
要实现真正的单次刷新(即旧 token 失效),你得自己加一层控制:
- 签发时在 payload 加唯一
jti(JWT ID),并存入 Redis,设置和 token 相同的过期时间 - 每次请求前,在自定义中间件里查 Redis:若
jti已存在且标记为 “revoked”,就拒绝 - 刷新接口里,先将旧
jti标记为 revoked,再生成带新jti的 token
注意:jwt.StandardClaims 支持 jti string 字段,但 fiber/jwt 默认不校验它——你需要在 SuccessHandler 或后续中间件里手动校验。
真正麻烦的不是代码,而是 Redis 连接可靠性、jti 生成碰撞概率、以及过期清理策略——这些细节比 JWT 签发本身更容易出问题。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










