mercure在symfony中发不出事件主因是jwt签名失败或topic不匹配:密钥需统一且编码一致,jwt payload必须含publish权限,topic字符串须前后端完全一致,nginx反代需透传upgrade/connection头。

Mercure 在 Symfony 中发不出事件,90% 不是代码写错了,而是 JWT 签名失败或 topic 不匹配——这两点必须对齐,否则 publish() 调用成功但前端收不到,连错误都不报。
JWT 签名失败:密钥、payload、编码三处必须一致
常见现象是 401 Unauthorized 或 curl 直连返回 {"error":"invalid_jwt"},日志里出现 "Unable to verify the JWT signature"。根本原因不是“没生成 token”,而是签名环节断在了某一处:
-
MERCURE_PUBLISHER_JWT_KEY和MERCURE_SUBSCRIBER_JWT_KEY必须完全相同,且编码方式一致(比如都 base64 编码,或都不编码);混用会静默失败 - JWT payload 至少要含
{"publish": ["*"]},若限制主题,必须精确匹配,例如{"publish": ["/api/posts/123"]}或{"publish": ["/api/posts/{id}"]} - Symfony MercureBundle 默认用
symfony/mercure客户端发事件,它不接受自定义 JWT 生成逻辑——你改了signing_key配置,但没同步更新环境变量里的密钥值,就会签不上
topic 字符串不匹配:前后端 URI 必须逐字一致
前端用 new EventSource('/.well-known/mercure?topic=/api/posts/123') 订阅,后端 publish() 就必须传完全相同的字符串,包括开头斜杠、大小写、通配符占位符。任何差异都会导致 Mercure Hub 过滤掉该事件:
- 别写成
/api/posts/123/(结尾多斜杠)或api/posts/123(缺开头斜杠) - 如果用了模板如
/api/posts/{id},发布时必须填具体值/api/posts/123,不能仍传模板字符串 - 开发时前端跑
http://localhost:3000,Mercure Hub 却配在https://localhost:8000?浏览器会因混合内容或 CORS 拦截连接,EventSource 打不开
publish() 参数传错:Update 实例不能省,data 必须是字符串
很多人直接调 $publisher->publish('/topic', $array),结果事件进不了 Hub——publish() 底层要求第一个参数是 Mercure\Update 实例,且 $data 必须是 JSON 字符串:
-
$data不能是 PHP 数组,得先json_encode($payload);传数组会导致 Hub 解析失败,静默丢弃 -
$topics必须是字符串数组,如['/api/posts/123'],不能是单个字符串'/api/posts/123' - 正确写法:
$update = new Update(['/api/posts/123'], json_encode(['title' => 'updated']));<br>$publisher->publish($update);
本地开发时 Hub 和 Symfony server 协议/端口不兼容
Symfony 的 server:start 默认启 HTTP,而 Mercure Hub(截至 2026 年 5 月)推荐 HTTPS + HTTP/2。两者协议不一致,会导致 Upgrade 头被丢弃、SSE 连接中断:
- Nginx 反代 Mercure Hub 时,必须显式透传
Upgrade和Connection头,否则 EventSource 降级为普通 HTTP 请求,收不到流 - 本地调试建议统一用 HTTPS:用
symfony server:ca:install配本地证书,再让 Mercure Hub 启--cert-file和--key-file - 绕过 Symfony 直连验证快速定位问题:
curl -v -X POST "$MERCURE_URL/.well-known/mercure" -H "Authorization: Bearer $JWT" -F "topic=/api/test" -F "data={\"msg\":\"ok\"}"
最易忽略的其实是环境变量和配置文件之间的密钥同步——.env 里改了 MERCURE_JWT_SECRET,但 config/packages/mercure.yaml 里还写着旧的 signing_key 值,或者 Docker 容器里挂载的 env 文件没更新,这类问题不会报错,只会让整个推送链路静默失效。











