Laravel 表单与 CSRF 保护

在 Web 应用中,表单是用户与后端交互的主要方式。Laravel 提供了简洁而强大的工具来处理表单数据,并内置了跨站请求伪造(CSRF)保护,防止恶意网站利用用户身份发起未授权请求。本章将详细介绍如何在 Laravel 中创建安全、规范的表单。

🔒 安全第一: Laravel 自动为每个活跃用户会话生成一个 CSRF 令牌,所有通过 POST、PUT、PATCH、DELETE 提交的表单都必须包含该令牌,否则请求会被拒绝。

1. 创建基本表单

在 Blade 模板中,你可以直接使用 HTML 构建表单。Laravel 推荐使用 POST 方法提交数据,并通过 @csrf 指令自动添加 CSRF 令牌字段。


<form method="POST" action="/profile">
    @csrf
    <div class="form-group">
        <label for="name">姓名</label>
        <input type="text" name="name" id="name" class="form-control">
    </div>
    <div class="form-group">
        <label for="email">邮箱</label>
        <input type="email" name="email" id="email" class="form-control">
    </div>
    <button type="submit" class="btn btn-primary">提交</button>
</form>
                    

生成的 HTML 中会自动包含一个隐藏的 CSRF 令牌输入框:


<input type="hidden" name="_token" value="随机生成的令牌">
                    

2. CSRF 保护原理

Laravel 的 CSRF 保护机制通过验证请求中的令牌与会话中存储的令牌是否一致来工作。如果令牌不匹配,Laravel 会抛出 TokenMismatchException 异常(通常显示 419 页面)。这可以有效防止跨站请求伪造攻击。

⚠️ 注意: 对于只读操作(如 GET 请求),Laravel 不要求 CSRF 令牌。但所有改变状态的请求(POST, PUT, PATCH, DELETE)都必须包含有效令牌。如果你使用 AJAX,需要在请求头中传递令牌(如 X-CSRF-TOKEN)。

3. 表单方法伪造(@method)

HTML 表单只支持 GET 和 POST 方法,但 Laravel 允许你通过 @method 指令伪造 PUT、PATCH 或 DELETE 请求,以便在路由中使用 RESTful 风格。


<form method="POST" action="/user/1">
    @csrf
    @method('PUT')
    <!-- 实际会生成一个隐藏的 _method 字段,值为 PUT -->
    <div class="form-group">
        <label>用户名</label>
        <input type="text" name="name" value="旧名称">
    </div>
    <button type="submit">更新</button>
</form>
                    

生成的 HTML 中会包含:


<input type="hidden" name="_method" value="PUT">
                    

这样 Laravel 的路由就能正确识别为 PUT 请求。

4. 处理表单验证错误

Laravel 提供了便捷的错误消息显示方式。当控制器验证失败时,会将错误信息闪存到 Session 中,并重定向回表单页面。你可以使用 @error 指令或 $errors 变量来显示错误。

显示所有错误


@if ($errors->any())
    <div class="alert alert-danger">
        <ul>
            @foreach ($errors->all() as $error)
                <li>{{ $error }}</li>
            @endforeach
        </ul>
    </div>
@endif
                    

针对特定字段显示错误


<input type="text" name="name" value="{{ old('name') }}">
@error('name')
    <div class="error-feedback">{{ $message }}</div>
@enderror
                    

5. 完整示例:用户注册表单

下面是一个完整的注册表单,包含了 CSRF 保护、验证错误显示、旧数据保留等功能。

📝 注册表单示例

<form method="POST" action="/register">
    @csrf

    <div class="form-group">
        <label for="name">姓名</label>
        <input type="text" name="name" id="name" class="form-control @error('name') is-invalid @enderror" value="{{ old('name') }}">
        @error('name')
            <div class="invalid-feedback">{{ $message }}</div>
        @enderror
    </div>

    <div class="form-group">
        <label for="email">邮箱</label>
        <input type="email" name="email" id="email" class="form-control @error('email') is-invalid @enderror" value="{{ old('email') }}">
        @error('email')
            <div class="invalid-feedback">{{ $message }}</div>
        @enderror
    </div>

    <div class="form-group">
        <label for="password">密码</label>
        <input type="password" name="password" id="password" class="form-control @error('password') is-invalid @enderror">
        @error('password')
            <div class="invalid-feedback">{{ $message }}</div>
        @enderror
    </div>

    <button type="submit" class="btn btn-primary">注册</button>
</form>
                        

对应的控制器验证规则示例:


public function register(Request $request)
{
    $validated = $request->validate([
        'name' => 'required|string|max:255',
        'email' => 'required|email|unique:users',
        'password' => 'required|min:8|confirmed',
    ]);

    // 创建用户...
    return redirect('/')->with('success', '注册成功!');
}
                    

6. AJAX 请求中的 CSRF 保护

当使用 JavaScript 发起 AJAX 请求时,需要手动将 CSRF 令牌添加到请求头中。Laravel 推荐在布局文件中将令牌存入 meta 标签,然后由 JavaScript 读取。

在页面头部添加 meta 标签:


<meta name="csrf-token" content="{{ csrf_token() }}">
                    

使用 jQuery 自动添加请求头:


$.ajaxSetup({
    headers: {
        'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
    }
});
                    

使用 Axios(默认已配置):


// Laravel 9+ 默认的 resources/js/bootstrap.js 已配置
axios.defaults.headers.common['X-CSRF-TOKEN'] = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
                    

7. 常见问题与最佳实践

  • CSRF 令牌过期: 如果页面长时间未操作,令牌可能会过期。可以捕获 419 错误并引导用户刷新页面或重新登录。
  • 排除特定路由的 CSRF 保护:app/Http/Middleware/VerifyCsrfToken.php$except 数组中添加路由,但请谨慎使用。
  • 表单的 action 使用命名路由: 推荐使用 route() 生成 URL,便于维护。
  • 使用 old() 保留输入: 验证失败后,使用 old('field') 可以保留用户之前输入的内容,提升体验。
  • 启用 HTTPS: 在生产环境中始终使用 HTTPS 传输敏感数据,CSRF 令牌在 HTTPS 下更安全。

8. 总结

Laravel 通过 CSRF 令牌为表单提交提供了开箱即用的安全防护。结合表单方法伪造、验证错误显示等功能,你可以快速构建出安全、用户友好的 Web 表单。记住:始终对所有状态变更请求使用 @csrf,并善用 old() 和错误处理来优化用户体验。

📚 深入学习: 参考官方文档 CSRF ProtectionValidation 了解更多高级用法。