FrankenPHP的Mercure仅通过JWT中mercure.subscribe字符串数组字段控制订阅权限,每个元素须为绝对URI或末尾带*的路径模板,不解析cookie、header或调用PHP校验,密钥必须与MERCURE_SUBSCRIBER_JWT_KEY严格一致。

FrankenPHP 的 Mercure 本身不自动鉴权用户身份,防止越权订阅必须靠 JWT payload 中的 mercure.subscribe 字段显式声明允许的主题范围,而不是依赖用户 ID 或 session。
mercure.subscribe 字段必须精确控制可订阅主题
FrankenPHP 的 Mercure Hub 只检查 JWT 的 mercure.subscribe 字段值,它是一个字符串数组,每个元素是允许订阅的 URI 模板(支持通配符)。Hub 不解析请求头、cookie 或 query 参数,也不调用你的 PHP 应用做二次校验。
- 合法写法示例:
["https://api.example.com/users/123/*", "https://api.example.com/notifications/me"] - 错误写法:
["*"]或["https://api.example.com/users/*"]—— 这等于开放全部用户数据 - 动态生成时,务必从当前登录用户上下文提取 ID,不能拼接未过滤的 URL 参数或表单字段
- 不支持正则或模糊匹配,只认字面量和
*(仅匹配一级路径段,如/users/123/*匹配/users/123/posts,但不匹配/users/123/profile/email)
JWT 必须由可信后端签发,且密钥与 FrankenPHP 完全一致
如果前端能自己构造 JWT,或密钥泄露,mercure.subscribe 就形同虚设。FrankenPHP 不校验 iss、sub 等标准字段,只验证 HS256 签名是否匹配环境变量 MERCURE_SUBSCRIBER_JWT_KEY。
- 永远不要在前端用 JS 生成 subscriber token;必须由 PHP 后端生成并返回(例如通过
/auth/mercure-token接口) - Docker 环境下,确保
-e MERCURE_SUBSCRIBER_JWT_KEY=xxx和 PHP 签发时用的密钥字符串完全相同(包括空格、换行) - 测试时可用
openssl dgst -sha256 -hmac "your_key" -binary验证签名逻辑是否对齐 - 避免密钥硬编码在 PHP 源码中;应从
$_ENV['MERCURE_SUBSCRIBER_JWT_KEY']或 Vault 获取
前端连接 Mercure 时不能绕过 token 校验
浏览器建立 EventSource 或 fetch 连接时,若把 token 放在 URL 参数(如 ?token=xxx),会被代理、CDN、浏览器历史记录泄露,且 Mercure Hub 默认不从 URL 解析 token。
立即学习“PHP免费学习笔记(深入)”;
- 必须通过
Authorization: Bearer <token>请求头传递 - 使用
EventSource时需用fetch+ReadableStream手动实现,或借助支持 headers 的封装库(原生EventSource不支持 header) - 若走反向代理(如 Caddy 把
/mercure代理到 Hub),确认代理配置未 stripAuthorization头 - Caddyfile 中 mercure 段落必须启用
subscriber_jwt指令,否则所有订阅请求直接 401
最易被忽略的是:FrankenPHP 不会帮你做「用户 A 能否订阅资源 B」的业务逻辑判断——这个判断必须在签发 JWT 前完成,并严格反映在 mercure.subscribe 数组里。漏掉一次 ID 绑定或宽泛通配,就等于打开数据越权通道。



















