退出登录后mercure仍收消息,根本原因是jwt未及时失效;应通过登出时将jti存入redis并签发新token前校验,结合短过期时间(如120s)与redis ttl(如180s)实现可靠失效。

退出登录后 Mercure 仍收消息,不是前端没关连接,而是后端没及时失效订阅凭证——JWT 还没过期,Hub 依然认为该用户合法在线。
为什么 eventSource.close() 不解决根本问题
浏览器页面卸载时 EventSource 确实会自动断开,但这个动作只影响客户端连接;FrankenPHP 内置的 Mercure Hub 不会立刻感知或清理对应会话。只要用户持有的 subscriber JWT 未过期,且 mercure.subscribe 字段仍匹配主题,任何新建立的 EventSource(比如用户快速重登、或另一个标签页)都能继续接收私有消息。
- 调用
eventSource.close()只是提前终止当前连接,对已签发的 JWT 无任何影响 -
window.onbeforeunload中发注销请求不可靠:进程可能被强杀、网络中断、或用户直接关机 - Mercure 协议本身不提供“退订”HTTP 接口,Hub 不接受 DELETE /subscriptions 这类操作
必须让 JWT 在登出时立即失效
真正可控的失效时机,是让后端在用户点击“退出”时,主动使当前 token 失效,并确保新连接无法再通过验证。FrankenPHP 的 Mercure 不支持黑名单机制,所以得靠“短生命周期 + 主动覆盖”来模拟即时失效:
- 登出接口(如
/auth/logout)中,将该用户的 JWT 签发时间戳和jti记入 Redis,设置 TTL 略长于 JWT 的exp(例如 exp=120s,则 Redis key TTL 设为 180s) - 在签发新 subscriber token 的 PHP 逻辑里(如
/auth/mercure-token),先查 Redis 是否存在该用户对应的已注销jti,若存在则拒绝签发 - 确保 Caddyfile 中的
subscriber_jwt配置启用了验证:mercure { subscriber_jwt {env.MERCURE_SUBSCRIBER_JWT_KEY} {env.MERCURE_PUBLISHER_JWT_KEY} } - 不要把密钥硬编码在 PHP 里;统一从
$_ENV['MERCURE_SUBSCRIBER_JWT_KEY']读取,与 FrankenPHP 启动时环境变量严格一致
避免前端泄露 token 导致绕过校验
即使后端做了 JWT 失效检查,如果前端把 token 拼在 URL 里(如 new EventSource('/.well-known/mercure?topic=...&token=xxx')),CDN、代理、浏览器历史都会缓存它,攻击者可复用该 URL 绕过登出逻辑。
- 必须通过
Authorization: Bearer <token></token>请求头传递 token - 前端需用
fetch()先获取 token,再用EventSource构造时传入headers(注意:原生EventSource不支持 headers,得用 polyfill 或改用fetch()+ReadableStream手动解析 SSE) - 测试时 curl 验证是否生效:
curl -H "Authorization: Bearer xxx" http://localhost/.well-known/mercure?topic=https://api.example.com/users/123/notifications,登出后应返回 401
最易被忽略的是:JWT 失效逻辑写在了登出接口里,但签发 token 的接口没做对应校验;或者 Redis key 的命名没包含用户维度(如只用 jti 而没加 user_id: 前缀),导致误判。实效性完全取决于这两端是否对齐。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











