JWT签名验证必须用ParseWithClaims配合正确密钥和SigningMethod,禁用ParseUnverified做权限判断;token传输需区分Cookie与Bearer Header并规范解析;refresh token须独立存储、绑定设备且一用即废;时钟偏移应通过leeway参数而非调系统时间解决。

JWT签名验证失败:ParseUnverified不是万能解药
直接用 ParseUnverified 读取 token 内容,再手动比对 exp 或 iss,看似省事,实则绕过了签名验证——攻击者可篡改 payload 后重签(若用弱密钥)或干脆伪造无签名 token。必须用 ParseWithClaims 配合正确密钥和 SigningMethod 实例。
- 常见错误:传入字符串密钥但未用
SigningMethodHS256.KeyFunc返回[]byte,导致验证静默失败 - HS 系列(如
HS256)密钥必须是[]byte;RS/ES 系列需用*rsa.PrivateKey/*ecdsa.PrivateKey和对应公钥验签 - 生产环境禁用
ParseUnverified做权限判断,它只适合调试或解析已知可信的 token(如日志分析)
token 存储与传输:别把 httpOnly Cookie 和 Bearer Header 搞混
前端存 token 的方式决定后端校验逻辑起点。用 Cookie 自动携带就走 Cookie 头解析;用 Authorization Header 就必须严格检查 Authorization: Bearer 格式,且不能忽略空格。
- 从
Cookie读 token 时,注意浏览器可能发送多个同名 Cookie(如路径不同),应取第一个或按 domain/path 精确匹配 - 从
Header读 token 时,用strings.TrimSpace清理前后空格,再用strings.HasPrefix判断是否以"Bearer "开头,避免因空格或大小写(如"bearer")被拒绝 - 若同时支持两种方式(不推荐),需定义明确优先级,比如 Header > Cookie,并记录日志防止混淆
刷新 token 的边界条件:RefreshToken 不是延长旧 token 有效期
标准做法是:旧 access token 过期前,用有效的 refresh token 换新 access token。关键点在于 refresh token 必须独立存储、绑定设备/IP/指纹,且每次使用后立即失效(或滚动更新)。
- 错误实践:仅延长原 access token 的
exp时间戳并重签——这等于没刷新,旧 token 仍可被重放 - refresh token 应设更长过期时间(如 7 天),但必须服务端记录其哈希值+状态,在 DB 或 Redis 中做存在性/有效性检查
- 如果用 JWT 实现 refresh token,务必加
jti字段防重放,并在签发新 access token 时嵌入当前 refresh token 的哈希摘要(可选)
时钟偏移导致 Token is expired:别急着调系统时间
客户端和服务端时间差超过 exp 容忍窗口(默认 0 秒),就会报错。硬调服务器时间风险高,应通过 JWT 库的校验参数控制。
立即学习“go语言免费学习笔记(深入)”;
- Gin-JWT 或
github.com/golang-jwt/jwt/v5支持WithValidTime或WithLeeway设置时间偏移容忍(如 120 秒) - 不要全局设置过大 leeway(如 300 秒),否则攻击者可在 token 过期后近 5 分钟内仍成功冒用
- 真正该修的是 NTP 同步:确保所有服务节点运行
systemd-timesyncd或chrony,而非依赖单点时间源
JWT 的“无状态”是假象——只要涉及黑名单、刷新、设备绑定,就得有状态存储。别为了追求无状态,把用户登出变成删 Cookie 这种不可靠操作。










