jwt黑名单必须用token.raw作key,且redis ttl应设为time.until(exptime);常见错误包括未trimspace、未trimprefix导致key不一致,及用tokenstring或固定ttl引发重复、漏判或内存浪费。

JWT黑名单必须用 token.Raw 作 key,不是字符串切片也不是带前缀的完整 header
直接用 authHeader 或 tokenString 当黑名单 key,会导致同一 token 被重复加入、查不到、或漏判。常见错误包括:
• strings.TrimSpace 没做,token 前后有空格或换行
• 没调 strings.TrimPrefix(authHeader, "Bearer "),导致存的是 "Bearer xxx" 而不是 "xxx"
• 解析后用了 tokenString,但 JWT base64url 填充规则可能导致不同编码结果;而 parsedToken.Raw 是签名前原始字节,稳定唯一
正确提取方式:rawToken := strings.TrimSpace(strings.TrimPrefix(c.GetHeader("Authorization"), "Bearer ")),验证时一律用 token.Raw
Redis 黑名单 TTL 必须等于 time.Until(expTime),不能设固定值
设固定 TTL(比如 24 小时)是最大误区。用户登出时 token 剩余有效期可能只剩 3 分钟,但黑名单项却挂 23 小时 57 分钟——浪费内存,还可能阻塞后续同名 key 写入。
写入黑名单时必须:
• 先用 jwt.ParseUnverified 快速解出 exp 字段(不验签名,快 10 倍以上)
• 计算 t := time.Until(time.Unix(exp, 0))
• Redis 写入用 SET token blacklisted EX [t.Seconds()],单位秒,向下取整
• 如果 t ,说明 token 已过期,无需写入黑名单
黑名单校验必须嵌入 keyFunc,不能放在中间件解析之后
很多实现把黑名单检查写在 jwt.Parse 完成之后,这是危险的——它绕过了 JWT 标准解析路径,刷新 Token、子服务透传、甚至自定义签名校验都可能跳过该检查。
安全做法是把校验逻辑塞进 keyFunc 回调里:
• keyFunc 被 jwt.Parse 调用前触发,天然强制所有解析路径走一遍
• 在 keyFunc 中先查 redisClient.Exists(ctx, tokenRaw),命中就直接返回 error
• 别用 GET 再判断空值,EXISTS 更快且防缓存穿透
• keyFunc 内部必须非阻塞:Redis 查询要用连接池 + ctx.WithTimeout 控制超时,本地 map 查 sync.Map.Load 是 OK 的
防爆刷要结合 IP + jti 双维度限流,不能只靠黑名单
黑名单只解决“已注销 token 失效”,对暴力撞库、高频刷新、短时间大量登录请求完全无效。
真正防爆刷得叠加两层:
• 请求级限流:用 X-Real-IP 或 X-Forwarded-For 提取真实 IP(注意配置 engine.SetTrustedProxies),每分钟限制 10 次登录请求
• Token 级限流:每个 jti(JWT 唯一 ID)绑定的 refresh 操作,24 小时内最多允许 3 次,用 Redis INCR + EXPIRE 原子完成
• 关键路径豁免:如 /healthz、/metrics 不参与限流,避免监控探针被误封
别忘了:限流规则必须热更新,用 atomic.Value 包裹 IPSet 和 map[string]int,避免 reload 配置时中间件看到脏数据
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











