419 page expired 是因表单缺少有效 csrf 令牌所致;laravel 默认对 web 路由强制校验,需用 @csrf 或 csrf_token() 正确注入,ajax 应通过 x-csrf-token 请求头传递,api 路由默认不校验。

为什么提交表单时总报 419 Page Expired?
这是 Laravel 默认开启 CSRF 防护后的典型表现——419 Page Expired 错误,本质是请求里没带有效 _token,或带了但已过期/不匹配。Laravel 在所有非 GET 表单提交(POST/PUT/PATCH/DELETE)中强制校验 CSRF 令牌,关不掉,也不该关。
关键不是“怎么开启”,而是“怎么让表单正确携带令牌”。默认情况下,Laravel 已经开启防护,你只需要确保每个表单都附上它。
- Blade 模板中用
@csrf指令即可生成隐藏输入项:<form method="POST" action="/users"> @csrf <input name="name"> </form>
- 手动写 HTML 时,必须显式插入:
<input type="hidden" name="_token" value="{{ csrf_token() }}"> - AJAX 请求需从页面 meta 标签或响应头读取,不能硬编码:
document.querySelector('meta[name="csrf-token"]').getAttribute('content')
Laravel 的 csrf_token() 和 @csrf 有什么区别?
csrf_token() 是一个辅助函数,返回当前会话的令牌字符串;@csrf 是 Blade 指令,底层调用的就是它,但额外做了安全处理:自动转义、确保只渲染一次、避免重复插入。
常见错误是混用二者导致双令牌:
- 在同一个表单里既写
@csrf又手动加<input name="_token" value="{{ csrf_token() }}">→ 后端收到两个_token,取第一个(可能为空或无效),直接 419 - 在 API 路由中误用:如果路由定义在
api.php且未加web中间件,则csrf_token()返回空字符串,因为 API 路由默认跳过 session 和 CSRF 中间件 - 多标签页同时打开同一表单页,反复刷新后令牌更新,旧页提交就会失败 —— 这不是 bug,是设计使然
哪些路由默认不校验 CSRF?
Laravel 只对绑定了 web 中间件组的路由做 CSRF 校验。换句话说:
-
routes/web.php中的路由默认有web中间件 → 必须带令牌 -
routes/api.php中的路由默认只有api中间件 → 不校验 CSRF,也无 session,csrf_token()无效 - 手动给 API 路由加
web中间件(如Route::post('/hook')->middleware('web'))→ 会触发校验,但通常不该这么做,API 应用 token 或 OAuth - 静态资源路径(如
/css/app.css)或中间件显式排除(VerifyCsrfToken::except(...))也不校验
为什么 AJAX 提交还是 419?
不是没传 _token,而是没传对地方。AJAX 默认不自动带表单字段,必须手动设置。
最稳妥的方式是把令牌塞进请求头(Laravel 默认检查 X-CSRF-TOKEN 头):
- 先在页面
里放 meta 标签:<meta name="csrf-token" content="{{ csrf_token() }}"> - 再用 JS 设置全局 axios 默认头:
axios.defaults.headers.common['X-CSRF-TOKEN'] = document.querySelector('meta[name="csrf-token"]').getAttribute('content'); - jQuery 用户可用:
$.ajaxSetup({ headers: { 'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content') } }); - 别用
data: { _token: 'xxx' }发送 —— 容易漏、难维护,且和 header 方式共存时可能冲突
CSRF 令牌绑定 session,每次新会话或调用 regenerate() 都会变。别缓存它,也别跨请求复用。










