Laravel 从框架底层就内置了 CSRF(跨站请求伪造)防护机制,但很多开发者只停留在“能用”层面,忽略了配置细节和不同场景下的失效风险。要真正防止表单请求伪造攻击,核心在于理解 VerifyCsrfToken 中间件的工作逻辑、正确输出 CSRF Token、合理处理 AJAX 请求,以及对排除规则做最小化配置。下面直接拆解具体配置方法和容易踩坑的地方。
CSRF 防护的默认工作机制Laravel 在 App\Http\Middleware\VerifyCsrfToken 中间件中自动校验所有 POST、PUT、PATCH、DELETE 请求的 CSRF Token。它要求请求中携带的 Token 与用户 Session 中存储的 Token 完全一致,否则抛出 TokenMismatchException,返回 419 状态码。这个机制默认全局启用,不需要额外注册中间件。但要注意,它只对 Web 中间件组生效,如果你在 routes/api.php 中定义接口,默认不走 Web 中间件组,也就不会校验 CSRF,这是 API 无状态认证与 Web 表单防护的合理区分。
表单中正确输出 Token在 Blade 模板中,最标准的做法是在 form 标签内部使用 @csrf 指令,它会自动生成一个隐藏的 input 字段,name 为 _token,value 为当前 Session 中的 CSRF Token。如果你需要手动控制,也可以使用 csrf_field() 函数,效果完全一样。对于纯 HTML 页面或非 Blade 模板,需要自己从 Session 中取出 Token 并放入表单。关键是确保每个会修改服务器状态的表单都携带这个隐藏字段,包括搜索筛选、删除确认、支付提交等,不要因为表单看起来“不敏感”就省略。
AJAX 请求的 Token 传递方案前端使用 fetch 或 axios 发起 POST 请求时,必须显式携带 CSRF Token。推荐的做法是在 HTML 的 meta 标签中输出 Token,然后用 JavaScript 统一读取并注入请求头。具体步骤:在 layout 模板的 head 中加入 <meta name="csrf-token" content="{{ csrf_token() }}">。然后在前端脚本中配置 axios 默认请求头:
axios.defaults.headers.common['X-CSRF-TOKEN'] = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
如果使用原生 fetch,需要在每次请求的 headers 中手动添加 X-CSRF-TOKEN 字段。Laravel 的 VerifyCsrfToken 中间件会同时检查表单字段 _token 和请求头 X-CSRF-TOKEN,两者任一匹配即可通过。对于 JSON 请求,还可以使用 X-XSRF-TOKEN 请求头,但需要从 Cookie 中读取 XSRF-TOKEN,这种方式依赖 Laravel 自动加密 Cookie,配置稍复杂,不如 meta 标签方案直观。
排除特定路由的注意事项有些场景需要关闭 CSRF 校验,比如第三方支付回调、Webhook 接收端点。Laravel 允许在 VerifyCsrfToken 中间件的 $except 数组中添加路由地址来排除。但这里有一个常见误区:很多人直接把整个控制器或通配符加进去,导致防护范围过大。正确做法是只排除确需外部系统直接 POST 的精确路径,例如:
protected $except = [
'payment/notify/alipay',
'webhook/github',
'stripe/webhook',
];
对于这类被排除的路由,必须使用其他机制来验证请求来源,比如签名校验、IP 白名单、共享密钥等,否则等于主动打开了一个 CSRF 攻击入口。如果业务允许,建议把回调处理放到 API 路由中,使用 Laravel 的签名 URL 或自定义中间件做来源验证,而不是简单粗暴地关闭 CSRF。
Session 配置对 CSRF 的影响CSRF Token 存储在 Session 中,因此 Session 的配置直接决定防护的可靠性。如果 Session 驱动使用 file 或 database,Token 会持久化在服务器端,安全性较高。但如果使用 cookie 驱动,整个 Session 数据都放在客户端 Cookie 中,虽然 Laravel 会加密,但一旦应用密钥泄露,攻击者可以伪造 Session 和 CSRF Token。生产环境务必使用 file、database、redis 等服务器端 Session 驱动,并确保 .env 中的 APP_KEY 足够随机且不对外泄露。另外,Session 的 secure 选项应设置为 true,让 Cookie 只在 HTTPS 下传输,防止中间人截获。
SameSite Cookie 属性的配合使用Laravel 默认在 config/session.php 中把 same_site 设置为 lax,这是比较平衡的选择。Lax 模式下,浏览器在跨站 POST 表单提交时不会携带 Cookie,从而在浏览器层面直接阻断大部分 CSRF 攻击。但 Lax 对 iframe 中的跨站请求、部分 AJAX 场景的防护不如 Strict 严格。如果你的应用不需要从第三方站点跳转后立即保持登录状态,可以把 same_site 改为 strict,防护更强。不过要注意,Strict 会导致用户从外部链接点击进入时首次请求不带 Cookie,可能影响用户体验。对于支付、后台管理等高风险模块,可以单独设置更严格的 Cookie 策略。
Token 过期与刷新机制CSRF Token 默认与 Session 生命周期绑定,Session 不过期,Token 就不会变。但如果你的应用在登录、权限变更等关键操作后调用 Session::regenerate(),CSRF Token 也会随之更新。此时如果前端页面还缓存着旧的 Token,后续请求就会 419 失败。解决方法是:在登录成功后重新加载页面,或者让前端从响应中读取新的 CSRF Token 并更新 meta 标签。对于 SPA 应用,建议在每次登录或重新认证后,从接口返回新的 csrf_token,前端同步更新 axios 默认请求头。
测试与验证防护是否生效配置完成后,不能只靠“感觉”判断 CSRF 防护是否正常。可以手动构造一个跨站表单,模拟攻击场景:在本地另一个域名下放置一个自动提交的 form,action 指向你的目标路由,不带 _token 字段,看是否返回 419。也可以使用 curl 命令直接 POST 不带 Token 的请求,观察响应状态码。对于 AJAX 请求,检查请求头中是否正确携带 X-CSRF-TOKEN。另外,Laravel 的测试框架中可以用 withoutMiddleware 跳过 CSRF 校验,但生产验证一定要走完整中间件链路。
常见错误与修复清单419 错误是最常见的 CSRF 相关报错。排查顺序:第一,确认表单中是否有 @csrf 或隐藏 _token 字段;第二,确认 Session 驱动是否正常写入,storage/framework/sessions 目录是否有权限;第三,确认浏览器 Cookie 中是否存在 laravel_session 和 XSRF-TOKEN;第四,确认 AJAX 请求是否携带了正确的请求头;第五,检查路由是否被错误地加入 $except 数组,或者中间件组配置是否正确。还有一个隐蔽问题:如果前端使用 Vite 或 Webpack 开发服务器,跨域请求时 Cookie 无法携带,需要配置代理或使用同源策略。
进阶:自定义 CSRF 校验逻辑对于高安全需求的应用,可以在 VerifyCsrfToken 中间件基础上扩展自定义校验。比如增加 Token 使用次数限制、绑定用户 IP 或 User-Agent、对敏感操作要求重新输入密码。但要注意,过度自定义可能破坏框架默认行为,增加维护成本。更务实的做法是保持框架原生机制,在业务层面增加二次验证,例如删除操作要求输入确认短语,支付操作要求短信验证码。CSRF 防护解决的是“请求是否来自可信页面”的问题,不能替代身份认证和业务授权。
Laravel 的 CSRF 防护本身足够可靠,真正的风险来自配置疏忽和业务场景误判。把 Token 输出、AJAX 注入、排除规则、Session 安全这四个环节逐一落实,再配合 SameSite Cookie 和 HTTPS,就能在框架层面有效阻断绝大多数表单请求伪造攻击。
