CSRF令牌校验失败的直接原因是FrankenPHP未正确传递Host和Origin头,导致Symfony生成与校验token时使用的请求域不一致;须配置可信代理、透传Host头并清除缓存验证。

CSRF 令牌在 FrankenPHP 下始终校验失败的直接原因
根本不是 Symfony 配置或表单写法的问题,而是 FrankenPHP 默认以 HTTP_X_FORWARDED_PROTO 和 HTTP_X_FORWARDED_FOR 等头判断请求上下文,但未正确传递原始请求的 Host 和 Origin 信息,导致 Symfony 的 CSRF token hash 计算时使用的「当前请求域」与生成 token 时的域不一致。
Symfony 如何生成和校验 CSRF token(关键逻辑)
Symfony 默认用 Request::getHost() + Request::getScheme() + form_name + session_id 拼接后做 hash,作为 token 的“种子”。如果请求进来的 Host 被 FrankenPHP 改写(比如从 app.local 变成 127.0.0.1:8080),哪怕 session 未变,hash 值就完全不同,校验必然失败。
- 检查实际生成的 token 种子:可在
vendor/symfony/form/Extension/Csrf/Type/FormTypeCsrfExtension.php中临时加dump($seed) - 对比开发环境(如 PHP built-in server)和 FrankenPHP 下的
$request->getHost()输出,通常不一致 -
csrf_token_id默认是表单名,一般没问题;真正出问题的是底层依赖的请求上下文
FrankenPHP 中必须设置的两个信任配置
FrankenPHP 不会自动信任反向代理头,必须显式声明哪些头可信,否则 Request 对象无法还原真实客户端信息。
- 在
public/index.php(或public/index.php被加载前的引导文件)中,添加:use Symfony\Component\HttpFoundation\Request; Request::setTrustedProxies(['127.0.0.1'], Request::HEADER_X_FORWARDED_ALL);
- 更重要的是:FrankenPHP 默认不转发
Host头。需在frankenphp.yaml或 Caddyfile 中显式透传:header_up Host {http.request.host} - 若使用 HTTPS 终止在 FrankenPHP 层,还需确保
X-Forwarded-Proto: https被设置且被 Symfony 信任(Request::HEADER_X_FORWARDED_PROTO)
验证是否修复成功的快速方式
不要等表单提交失败再排查,用最简路径确认请求上下文已一致:
立即学习“PHP免费学习笔记(深入)”;
- 在控制器里加一行:
dump($request->getHost(), $request->getScheme(), $request->headers->get('origin')); - 用 curl 模拟提交,观察输出是否与浏览器地址栏一致(例如都是
app.local+https) - 清除浏览器 cookie 和 Symfony cache:
rm -rf var/cache/*,避免旧 session 中残留错误域的 token seed - 如果仍失败,检查 FrankenPHP 日志是否报
Invalid CSRF token并附带expected ... got ...—— 两段 hash 值差异就是域信息错位的铁证
CSRF 校验失败本身不报具体原因,只抛 Symfony\Component\Form\Exception\LogicException,所以必须从请求上下文一致性入手。FrankenPHP 的轻量级设计让它不会自动补全 Webserver 常见行为,这点和 Nginx + PHP-FPM 组合很不同。



















