必须用Redis而非内存map,因多实例下map无法共享黑名单,Redis提供跨进程可见性、原子操作和自动过期能力;Token注销需用SET+EX原子写入,校验须在JWT签名和时效验证通过后执行黑名单检查。

为什么黑名单必须用 Redis 而不是内存 map
内存 map 在多实例部署下完全失效,请求可能打到没同步黑名单的节点;Redis 提供原子性操作和跨进程可见性,是唯一靠谱选择。别图省事用 sync.Map,上线后 Token 黑名单漏拦截是必然的。
实操建议:
- 用
redis.Set存 Token(带过期时间),不是redis.String—— 避免重复写入和并发竞争 - 黑名单 TTL 必须 ≥ Token 原有过期时间,否则提前失效导致误放行
- 推荐用
redis.NewClient+WithContext,别用全局 client 变量,避免 context 泄漏
Gin 中间件怎么正确校验并拦截黑名单 Token
不能只在 jwt.Parse 后查黑名单——Token 本身已过期时,jwt.Parse 会直接 panic 或返回 error,根本走不到黑名单检查逻辑。必须先解析、再验证签名和时效、最后查黑名单。
关键代码结构:
func AuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
authHeader := c.GetHeader("Authorization")
if authHeader == "" {
c.AbortWithStatusJSON(401, gin.H{"error": "missing token"})
return
}
tokenStr := strings.TrimPrefix(authHeader, "Bearer ")
<pre class="brush:php;toolbar:false;"> token, err := jwt.Parse(tokenStr, func(t *jwt.Token) (interface{}, error) {
return []byte(os.Getenv("JWT_SECRET")), nil
})
if err != nil || !token.Valid {
c.AbortWithStatusJSON(401, gin.H{"error": "invalid token"})
return
}
// 此时 token 已确认有效,才查黑名单
exists, err := redisClient.Exists(context.Background(), "blacklist:"+tokenStr).Result()
if err != nil {
c.AbortWithStatusJSON(500, gin.H{"error": "redis error"})
return
}
if exists > 0 {
c.AbortWithStatusJSON(401, gin.H{"error": "token revoked"})
return
}
c.Next()
}}
Token 注销时如何保证原子性写入黑名单
用户登出或密码修改时,需要把当前 Token 加入黑名单。常见错误是:先删 Redis key 再发注销响应,结果请求还没收到响应就重放了旧 Token。
Go语言(Golang)1.26.0版本提供 Go 官方 Windows amd64 MSI 安装包下载入口,版本号 1.26.0,可用于旧项目维护、兼容性测试和指定版本开发环境配置。
安全做法:
- 用
redis.Set的EX参数一次性写入并设 TTL,不要分Set+Expire两步 - TTL 值取
token.ExpiresAt - time.Now().Unix(),而不是硬编码(比如 7200 秒)——避免 Token 剩余 10 秒时还设 2 小时过期 - 注销接口返回前,必须等待
redis.Set返回成功,不能异步写入
性能瓶颈在哪?怎么压测验证
单次鉴权要串行执行 3 次 Redis 操作(Parse → Check expiry → Check blacklist),在 QPS > 5k 场景下容易成为瓶颈。实际压测发现,90% 耗时卡在 redis.Client.Get 的网络往返上。
优化点:
- 用
redis.Pipeline把黑名单检查和后续业务查询合并,但注意 Gin 中间件里不适合做 pipeline(上下文生命周期短) - 更现实的方案:给 Redis 加连接池,
redis.Options.PoolSize至少设为 CPU 核数 × 4 - 压测时重点看
redis_client_latency_ms和gin_total_time_ms的 P99 分位,不是平均值
别忽略一点:黑名单 key 的命名必须包含签发时间戳或 jti 字段,否则不同用户用相同 payload 签发的 Token 会互相误杀。用 "blacklist:" + jti 是最稳妥的。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










