frankenphp的mercure hub默认不支持eventsource带authorization头订阅,因浏览器规范禁止其设置自定义请求头;正确方式是通过secure+samesite=lax的mercureauthorization cookie传递jwt,且payload须含mercure.subscribe声明。

FrankenPHP 的 Mercure Hub 默认不接受带认证的 EventSource 订阅,因为浏览器原生 EventSource 不支持自定义请求头(如 Authorization),也无法在 URL 中安全传递 token —— 这不是配置遗漏,而是协议和客户端限制决定的。
为什么 EventSource 无法发送 Authorization 头
这是浏览器规范硬性限制:EventSource 只允许携带 cookie 或通过 URL 传参,不能设置任意请求头。试图在 JS 里写 new EventSource(url, { headers: {...} }) 会直接报错或被忽略。
- cookie 方式可行,但要求后端设置
withCredentials: true且域名、路径、Secure/HttpOnly 属性匹配 - URL 参数方式(如
?token=xxx)虽能传,但 token 会暴露在服务端日志、代理记录、浏览器地址栏历史中,不符合 OAuth/Bearer token 使用规范 - Mercure Hub 的 JWT 验证逻辑本身支持
Authorization头,但前提是请求能抵达 Hub —— 而EventSource根本发不出这个头
正确做法:用 cookie + SameSite + Secure 配合 Mercure JWT 验证
FrankenPHP 内置 Mercure Hub 支持从 cookie 提取 JWT,前提是 cookie 名为 mercureAuthorization(可配),且签名密钥与 Hub 一致。这不是“绕过认证”,而是 Mercure 协议官方推荐的浏览器端认证方式。
- PHP 后端登录成功后,用
setcookie('mercureAuthorization', $jwt, [...])设置 HttpOnly=false、Secure=true、SameSite=Lax 的 cookie - Caddyfile 中确保 Mercure Hub 配置了
publish_origins和allowed_origins,否则即使 cookie 正确也会被拒绝 - 前端创建
EventSource时**不要加任何参数**,让浏览器自动带上 cookie:new EventSource('/.well-known/mercure?topic=https://example.com/user/{id}') - JWT payload 必须包含
mercure.subscribe声明,例如:{"mercure": {"subscribe": ["https://example.com/user/123"]}}
如果必须用 Bearer token,就得换客户端方案
放弃 EventSource,改用 fetch() 轮询或 WebSocket(需额外部署 WS 服务),或者用 Symfony Mercure Bundle 提供的 MercurePublisher + 自定义鉴权中间件拦截 POST 请求。但这已脱离 FrankenPHP “零依赖实时推送”的设计初衷。
-
mercure_publish()函数本身不校验订阅者权限,只负责向 Hub 发布;权限控制完全在 Hub 的 JWT 解析阶段完成 - 若强行在 PHP 层做订阅前校验(比如读 session 判断用户是否能看某 topic),需自己实现
/mercure-subscribe代理路由,并返回重定向到 Mercure Hub 的带签名 URL —— 但这样就失去了 Server-Sent Events 的流式优势 - FrankenPHP 的
mercureCaddy.FindHub()返回的 Hub 实例默认启用 JWT 验证,关闭它(disable_jwt_bearer)等于放弃认证,不推荐
真正容易被忽略的是:Mercure Hub 对 cookie 的解析是大小写敏感的,且只认 mercureAuthorization 这个固定名(除非你改源码重新编译)。设成 auth_token 或 mercure_token 都不会生效。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











