CSRF(Cross-Site Request Forgery)是一种冒充已登录用户,让其发送非本人意图请求的攻击。
例如,你的应用有一个 POST /user/email 接口,用于修改邮箱地址。如果攻击者在另一个网站上放置了自动向该 URL 提交的表单,那么用户可能在毫无察觉的情况下被修改了邮箱。
在 Laravel 13 中,由于 web 中间件组已内置相关机制,CSRF 保护默认处于启用状态。
防御 CSRF 攻击
Laravel 的 PreventRequestForgery 中间件通过以下两个层面防御 CSRF:
- Origin 校验(
Sec-Fetch-Site 头)
- 令牌校验(会话级 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 路由中创建 POST、PUT、PATCH、DELETE 表单时,务必包含 @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。