黑名单失效本质是校验环节被绕过:需确保黑名单写入正确、每次请求真实校验、读写key严格一致,且续签接口独立校验refresh_token黑名单。

Hyperf 3.1 中使用 JWT 认证组件实现自动续期时,若出现“黑名单失效”问题——即本该被拉黑的 token(如登出、换密、踢人后)仍能继续访问接口——本质不是 JWT 解析错了,而是黑名单校验环节被绕过或未生效。核心要盯住三点:黑名单是否真写进去了、每次请求是否真查了、查的 key 和写入的是否一致。
黑名单没写进去或写错位置
常见错误是只在登出逻辑里删 token,却没把 refreshToken 加入黑名单;或者只加了 access_token 黑名单,但续签流程压根不校验它。Hyperf 的 JWT 组件默认不自带黑名单写入逻辑,需手动在登出、密码修改、强制下线等业务点调用 Redis 操作:
- 登出时:用 redis->del("blacklist:{$accessToken}") 或 redis->sAdd("rt:blacklist", $refreshToken)(推荐后者,更聚焦 refresh 流程)
- 续签接口(
/auth/refresh)中,必须在签发新 token 前,把当前传入的旧 refreshToken 加入黑名单,且设置合理过期时间(比如比 refresh_token 多 5 分钟) - 避免用
set存黑名单,改用setex或sAdd+expire,否则黑名单永不过期或直接丢失
每次请求没走黑名单校验
Hyperf 的 JWT 中间件(如 JwtMiddleware)默认只做签名和 exp 校验,不查黑名单。你得在中间件链里显式插入自定义校验逻辑,且确保它在 JWT 解析之后、业务控制器之前执行:
- 新建一个
BlacklistCheckMiddleware,在handle()中解析出 token 后,先取 payload 里的 jti 或用户 ID + 设备指纹拼 key,再查 Redis 是否存在 - 检查中间件注册顺序:必须在
JwtMiddleware之后,否则拿不到已解析的 user_id;不能放在 CORS 或日志中间件之后,否则可能被跳过 - 别依赖全局事件监听(如
OnRequest),Hyperf 的中间件执行链才是唯一可控入口
key 不一致导致“查了也白查”
写入和查询用的 key 对不上,是最隐蔽也最常发生的错误。例如:
- 写入时用
"blacklist:" . md5($token),查询时却用"blacklist:" . $token - refresh_token 存的是原始字符串,但校验时误用 base64url 安全编码后的值当 key
- 多实例部署时,Redis 连接配置指向不同库(db 0 vs db 1),一个实例写进去,另一个实例查不到
- 开发环境用 file cache,生产才切 Redis,忘了改配置,黑名单实际没落地
续期逻辑本身绕过黑名单
如果自动续期是在 access_token 快过期时由前端静默触发(比如 TTL 剩 2 分钟就调 /auth/refresh),那这个接口本身必须独立校验 refreshToken 黑名单——而不能只靠 access_token 中间件。否则攻击者只要持有任意一个未过期的 access_token,就能无限刷出新 token。
-
/auth/refresh接口必须单独加RefreshTokenCheckMiddleware,且校验逻辑比 access_token 更严:查黑名单 + 验签 + 检查绑定设备(可选) - 不要在 access_token 过期前自动刷新并返回新 token(即“每次请求都返新 token”模式),这会放大并发刷新风险,也削弱黑名单意义
- 刷新成功后,务必立即作废旧 refreshToken,并返回新 pair,客户端需原子性更新本地存储


















