跳转到主要内容

简介

CSRF(Cross-Site Request Forgery)是一种冒充已登录用户,让其发送非本人意图请求的攻击。 例如,你的应用有一个 POST /user/email 接口,用于修改邮箱地址。如果攻击者在另一个网站上放置了自动向该 URL 提交的表单,那么用户可能在毫无察觉的情况下被修改了邮箱。 在 Laravel 13 中,由于 web 中间件组已内置相关机制,CSRF 保护默认处于启用状态。

防御 CSRF 攻击

Laravel 的 PreventRequestForgery 中间件通过以下两个层面防御 CSRF:
  1. Origin 校验Sec-Fetch-Site 头)
  2. 令牌校验(会话级 CSRF 令牌)
它首先检查 Origin,若无法判定或校验失败,则回退到令牌校验。

Origin 校验

Laravel 首先检查 Sec-Fetch-Site,判断请求是否来自同一 Origin。这在 HTTPS 环境下尤其有效。 若 Origin 校验通过,则该请求会被直接放行。若未通过,则会像以往那样执行 CSRF 令牌校验。
Sec-Fetch-Site 的前提是通过 HTTPS 连接使用。在 HTTP 环境下 Origin 校验不会生效,令牌校验将成为主要的防御手段。

Origin-only 模式

你可以禁用令牌校验的回退,仅通过 Origin 校验来判断。
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Middleware;

return Application::configure(basePath: dirname(__DIR__))
    ->withMiddleware(function (Middleware $middleware): void {
        $middleware->preventRequestForgery(originOnly: true);
    });
在 Origin-only 模式下,Origin 校验失败的请求将返回 403 而不是 419 如果你希望允许同站请求(例如跨子域请求),可以设置 allowSameSite
$middleware->preventRequestForgery(allowSameSite: true);

令牌校验

Laravel 会为每个会话生成 CSRF 令牌。可以通过 csrf_token() 或会话获取。
use Illuminate\Http\Request;

Route::get('/token', function (Request $request) {
    $tokenFromSession = $request->session()->token();
    $tokenFromHelper = csrf_token();
});
web 路由中创建 POSTPUTPATCHDELETE 表单时,务必包含 @csrf
<form method="POST" action="/profile">
    @csrf

    <!-- 等效的 hidden input -->
    <input type="hidden" name="_token" value="{{ csrf_token() }}" />
</form>

排除 URI

对于像 Stripe Webhook 这样由外部服务发来的请求,有时需要将特定 URI 从 CSRF 保护中排除。
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Middleware;

return Application::configure(basePath: dirname(__DIR__))
    ->withMiddleware(function (Middleware $middleware): void {
        $middleware->preventRequestForgery(except: [
            'stripe/*',
            'http://example.com/foo/bar',
            'http://example.com/foo/*',
        ]);
    });
如果可能,请将 Webhook 路由放在 web 中间件组之外,并将排除设置控制在最小范围。

X-CSRF-TOKEN 头

Laravel 不仅会校验表单中的 _token,也会校验 X-CSRF-TOKEN 头。 首先,将令牌输出到 meta 标签。
<meta name="csrf-token" content="{{ csrf_token() }}">
然后将该值添加到 AJAX 请求头中。
$.ajaxSetup({
    headers: {
        'X-CSRF-TOKEN': document
            .querySelector('meta[name="csrf-token"]')
            .getAttribute('content'),
    },
});

X-XSRF-TOKEN 头

Laravel 还会发送加密过的 XSRF-TOKEN Cookie。Axios 或 Angular 在发送同源请求时,会自动将该值设置到 X-XSRF-TOKEN 头中。 因此,在 SPA 或 AJAX 实现中,某些情况下即使不手动编写请求头设置,CSRF 保护也能生效。

SPA 的注意事项

当在 SPA 中将 Laravel 作为 API 后端使用时,需要先获取 CSRF Cookie,再发送登录请求。
await axios.get('/sanctum/csrf-cookie');
await axios.post('/login', {
    email: '[email protected]',
    password: 'password',
});
详细内容请参阅 Sanctum
最后修改于 2026年7月13日