Laravel的VerifyCsrfToken中间件通过比对请求中的_token(优先X-CSRF-TOKEN头,其次表单字段)与session中存储的_token值来验证合法性,依赖session存活、路由匹配web中间件组、中间件执行顺序三个硬条件,缺一不可。

CSRF Token 验证不是“加个 @csrf 就完事”,它依赖 session 存活、请求路径匹配、中间件执行顺序三个硬条件,缺一不可。
VerifyCsrfToken 中间件怎么判断请求是否合法
该中间件在 App\Http\Middleware\VerifyCsrfToken 中定义,核心逻辑是:从请求中提取 token(优先检查 X-CSRF-TOKEN 请求头,其次检查表单 _token 字段),再与当前 session 中的 _token 值比对。
关键点:
- session 必须已启动且未过期;若用户长时间无操作,session 销毁后 token 丢失,后续所有 POST/PUT/DELETE 请求都会返回
419 Page Expired - 中间件只对
web中间件组生效,默认不作用于api路由——所以api/下的路由天然豁免,但这也意味着你不能靠它保护 API 端点 - 如果请求 URL 匹配
$except数组中的路径(如webhook/stripe),中间件直接跳过验证,不会抛异常
@csrf 指令生成的 hidden 字段为什么有时不生效
@csrf 编译后生成的是 <input type="hidden" name="_token" value="xxx">,但它只在 Blade 模板中起作用,且依赖两个前提:
- 当前响应必须由 Laravel 渲染(即走
web路由),否则 session 不写入、token 不生成 - 不能在非 session 上下文中调用,比如通过
php artisan tinker手动渲染 Blade 片段,@csrf会输出空字符串 - 如果页面被 CDN 缓存或服务端启用了静态 HTML 输出,
@csrf会被固化成旧值,导致后续提交失败
常见错误现象:TokenMismatchException 或 419,但浏览器开发者工具里能看到 _token 字段存在——说明问题不在字段缺失,而在 session 和字段值不同步。
AJAX 请求如何正确携带 CSRF Token
Laravel 不自动为 AJAX 设置 token,必须手动注入。最稳妥的方式是从 <meta name="csrf-token"> 标签读取:
<meta name="csrf-token" content="{{ csrf_token() }}">
然后在 JS 中设置全局 header:
axios.defaults.headers.common['X-CSRF-TOKEN'] = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
注意:
- 这个
<meta>标签必须出现在 HTML 的中,且由 Laravel 渲染(不能是纯静态 HTML) - 使用
fetch时需显式传入:headers: { 'X-CSRF-TOKEN': token },它不会像 axios 那样自动继承 default headers - 如果前端是独立部署的 SPA(如 Vue/Vite 项目),后端必须通过接口返回 token,或配置 CORS 允许读取 cookie + session,否则无法共享 session 中的 token
为什么有些 POST 请求没报错却绕过了 CSRF 验证
这不是漏洞,而是 Laravel 的显式放行机制在起作用:
- 路由前缀匹配
$except:比如你在VerifyCsrfToken.php中写了protected $except = ['api/*'],那所有/api/v1/user都跳过验证 - HTTP 方法被忽略:CSRF 中间件默认只拦截
POST、PUT、PATCH、DELETE,但如果你用GET提交敏感操作(例如GET /user/delete?id=123),它完全不校验——这是设计缺陷,不是防护失效 - 中间件未注册到路由组:如果你用
Route::middleware(['api'])定义路由,而api组里没包含VerifyCsrfToken,那自然不验证
真正容易被忽略的是:CSRF 防护和认证是两件事。即使你关掉了 VerifyCsrfToken,只要还用 auth 中间件,用户依然要登录;但登录成功 ≠ 请求合法——攻击者可以诱导已登录用户点击恶意链接,直接触发无 token 的 POST。


















