laravel 文件上传必须同时满足 post 方法、multipart/form-data 编码和表单内前置 @csrf 指令,缺一导致 419 错误;ajax 上传需通过 x-csrf-token 请求头传递 token,不可重复添加 _token 字段;仅 webhook 或 api 场景可豁免 csrf 校验。

在 Laravel 中构建文件上传表单时,必须同时满足文件上传的 multipart/form-data 编码要求和 CSRF 防护强制校验规则,漏掉任一环节都会导致 419 Page Expired 或 TokenMismatchException 错误,且该问题在 Chrome 多标签页切换、Nginx 反向代理或 CDN 缓存后尤为高频。
确认表单结构与 CSRF 指令位置
打开 Blade 模板文件(如 resources/views/upload.blade.php),检查表单是否同时满足三项硬性条件:method 必须为 POST、enctype 必须为 multipart/form-data、@csrf 必须位于 <form></form> 标签内部且在任何 <input type="file"> 之前。
正确写法示例:
【@csrf 必须无条件渲染,不能包裹在 @if、@unless 或注释中】——否则浏览器源代码里看不到 <input name="_token">,VerifyCsrfToken 中间件会直接拦截并返回 419。
验证 CSRF Token 是否真实注入到 HTML
在浏览器中打开该上传页面 → 右键 → “查看网页源代码” → 搜索 _token。
若看到类似 <input type="hidden" name="_token" value="abc123..."> 的字段,说明 @csrf 已生效;若搜索不到,说明 Blade 渲染失败或被逻辑屏蔽。
常见陷阱:使用了 CDN 缓存整个 HTML 页面,导致 @csrf 渲染出的旧 token 被固化;或在 Nginx 配置中启用了 proxy_cache_bypass $cookie_session 却未排除 _token 字段,造成 token 值与 session 不同步。
AJAX 文件上传时手动注入 CSRF Token
当用 axios 或 fetch 上传文件(例如拖拽上传、分片上传),不能依赖 @csrf,必须显式携带 X-CSRF-TOKEN 请求头。
第一步:确保页面 中存在 Laravel 自动注入的 meta 标签:
第二步:在 JS 中读取该值并设置全局请求头:axios.defaults.headers.common['X-CSRF-TOKEN'] = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
第三步:发起 FormData 请求时,无需再手动 append _token 字段——VerifyCsrfToken 中间件会优先从 X-CSRF-TOKEN 请求头提取 token 并比对 session 值。
【切勿在 FormData 中重复添加 _token 字段】——这会导致中间件同时收到 header 和表单字段两个 token,而 Laravel 默认只认 header,表单字段会被忽略,但若 header 缺失则 fallback 到字段,逻辑混乱易踩坑。
绕过 CSRF 校验的唯一安全路径
仅当上传接口明确属于第三方 Webhook(如微信头像回调、Stripe 文件上传通知)或纯 API 场景(前端已通过 Sanctum 认证且不依赖 Cookie)时,才可跳过 CSRF 校验。
方法一:将路由定义在 routes/api.php 中 → 自动豁免 VerifyCsrfToken 中间件;
方法二:若必须放在 routes/web.php,在 app/Http/Middleware/VerifyCsrfToken.php 的 $except 数组中添加前缀路径:protected $except = [ 'webhook/upload/*' ];
注意:$except 只支持前缀匹配,不支持正则或通配符嵌套,写成 'webhook/*/callback' 无效。











