引言

随着现代 Web 应用日益采用单页应用(SPA)架构,实现安全的身份验证变得至关重要。Laravel Sanctum 为 SPA 身份验证提供了优雅的解决方案,相比传统基于令牌的方案具有更高的安全性。本指南将带您创建 Laravel API 应用,并实现基于 Cookie 的 Sanctum 身份验证。

什么是基于 Cookie 的身份验证?

基于 Cookie 的身份验证利用 HTTP-only Cookie 安全存储会话信息。与 localStorage 或 sessionStorage 存储的令牌不同,HTTP-only Cookie 无法被 JavaScript 访问,天然具备防御 XSS 攻击的能力。

Laravel Sanctum 的基于 Cookie 的身份验证专为前端与后端共享相同顶级域名的 SPA 设计,无需将敏感令牌暴露给客户端 JavaScript 即可实现无缝身份验证。

为何选择基于 Cookie 而非令牌?

  • XSS 防护:HTTP-only Cookie 无法被 JavaScript 访问,防止 XSS 攻击窃取令牌
  • 自动管理:浏览器自动处理 Cookie 存储和传输
  • 会话安全:Laravel 内置会话管理提供额外安全层

开发者体验优势

  • 无缝集成:与 Laravel 现有身份验证系统自然协作
  • 免令牌管理:无需手动处理令牌存储、刷新或过期
  • 内置 CSRF 保护:利用 Laravel 的 CSRF 防护机制

创建 Laravel 项目

创建专为 API 开发优化的 Laravel 应用:

laravel new sanctum-api
cd sanctum-api

选择 “No Starter Kit”(无入门套件)以手动配置完整流程。

安装配置 Sanctum

步骤 1:安装 Sanctum API

php artisan install:api

此命令自动完成:

  • 安装 Laravel Sanctum 包
  • 创建带中间件的 routes/api.php 文件
  • bootstrap/app.php 配置 Sanctum 中间件
  • 注册 API 路由

步骤 2:配置有状态 API

bootstrap/app.php 中配置:

->withMiddleware(function (Middleware $middleware) {
    $middleware->statefulApi(); // 启用基于Cookie的身份验证
})

步骤 3:配置 Sanctum 设置

编辑 config/sanctum.php

'prefix' => 'api', // 统一API前缀
'middleware' => [
    'authenticate_session' => Laravel\Sanctum\Http\Middleware\AuthenticateSession::class,
    'encrypt_cookies' => App\Http\Middleware\EncryptCookies::class,
    'validate_csrf_token' => App\Http\Middleware\VerifyCsrfToken::class,
],

步骤 4:配置前端域名

.env 文件中指定:

# 开发环境
SANCTUM_STATEFUL_DOMAINS=localhost:3000,127.0.0.1:3000

# 生产环境
SANCTUM_STATEFUL_DOMAINS=myapp.com,api.myapp.com

关键配置说明

  • 不包含 http://https://
  • 不添加开头/结尾斜杠
  • 包含非标准端口
  • 前端与后端必须共享相同顶级域名

创建身份验证系统

步骤 1:创建控制器

php artisan make:controller AuthController

步骤 2:实现登录/退出逻辑

class AuthController extends Controller
{
    public function login(Request $request): JsonResponse
    {
        $credentials = $request->validate([
            'email' => ['required', 'email'],
            'password' => ['required', 'string'],
        ]);
        
        if (Auth::attempt($credentials)) {
            $request->session()->regenerate(); // 重新生成会话ID
            
            return response()->json([
                'message' => '身份验证成功',
                'user' => Auth::user(),
            ]);
        }
        
        throw ValidationException::withMessages([
            'email' => ['提供的凭据不正确'],
        ]);
    }
    
    public function logout(Request $request): JsonResponse
    {
        Auth::logout();
        $request->session()->invalidate();
        $request->session()->regenerateToken();
        
        return response()->json(['message' => '已成功退出']);
    }
    
    public function user(): JsonResponse
    {
        return response()->json(['user' => Auth::user()]);
    }
}

步骤 3:注册路由

routes/api.php 中添加:

Route::prefix('auth')->group(function () {
    Route::post('login', [AuthController::class, 'login']);
    Route::post('logout', [AuthController::class, 'logout'])->middleware('auth:sanctum');
});

Route::middleware('auth:sanctum')->get('user', [AuthController::class, 'user']);

设置测试数据

php artisan db:seed

默认创建用户:

  • 邮箱test@example.com
  • 密码password

自定义测试数据可修改 DatabaseSeeder.php


身份验证流程

认证流程步骤:

  1. 获取 CSRF Cookie:从 Laravel 获取 CSRF 令牌
  2. 登录请求:使用凭据和 CSRF 令牌认证
  3. 认证请求:使用会话 Cookie 进行后续请求

示例请求:

### 1. 获取CSRF Cookie
GET /api/sanctum/csrf-cookie
Origin: localhost

### 2. 登录(使用提取的XSRF-TOKEN)
POST /api/auth/login
X-XSRF-TOKEN: [令牌值]
Content-Type: application/json

{
    "email": "test@example.com",
    "password": "password"
}

### 3. 访问受保护路由
GET /api/user
X-XSRF-TOKEN: [令牌值]

常见错误解决方案

错误 1:“Session store not set on request” (500错误)

原因statefulApi() 需要 OriginReferer 标头验证请求来源
解决:所有请求添加 Origin 标头:

Origin: localhost

错误 2:“CSRF token mismatch” (419错误)

解决步骤

  1. 先请求 /api/sanctum/csrf-cookie
  2. 正确提取 Cookie 值(移除 URL 编码)
  3. 所有修改状态请求添加 X-XSRF-TOKEN 标头
  4. 检查 Cookie 是否过期

错误 3:“Unauthenticated” (401错误)

排查方向

  • 请求缺失会话 Cookie
  • 会话过期或无效
  • 前端域名未在 SANCTUM_STATEFUL_DOMAINS 中配置

错误 4:“Preflight wildcard origin not allowed”

解决:在 config/cors.php 配置具体域名:

'allowed_origins' => ['http://localhost:3000'],
'supports_credentials' => true, // 关键配置

前端集成示例

Axios 配置

const api = axios.create({
    baseURL: 'http://localhost:8000/api',
    withCredentials: true, // 启用Cookie传输
});

// 自动添加CSRF令牌
api.interceptors.request.use(config => {
    const token = document.cookie.match('XSRF-TOKEN=([^;]+)')?.[1];
    if (token) config.headers['X-XSRF-TOKEN'] = token;
    return config;
});

Vue.js 身份验证服务

class AuthService {
    async login(credentials) {
        await api.get('/sanctum/csrf-cookie'); // 先获取CSRF
        return api.post('/auth/login', credentials);
    }
    
    async logout() {
        return api.post('/auth/logout');
    }
}

React 认证钩子

function useAuth() {
    const [user, setUser] = useState(null);
    
    useEffect(() => { checkAuth() }, []);
    
    const checkAuth = async () => {
        try {
            const { data } = await api.get('/user');
            setUser(data.user);
        } catch {
            setUser(null);
        }
    };
    
    // 登录/退出方法
    return { user, login, logout };
}

安全最佳实践

会话管理加固

public function login(Request $request) {
    if (Auth::attempt($credentials)) {
        $request->session()->regenerate(); // 认证后重新生成会话ID
        // ...
    }
}

生产环境配置

# .env
APP_ENV=production
APP_DEBUG=false
SESSION_SECURE_COOKIE=true # 仅HTTPS传输Cookie
SESSION_SAME_SITE=lax

速率限制

// 限制认证接口请求频率
Route::middleware('throttle:auth')->post('/auth/login', ...);

强制 HTTPS

// AppServiceProvider.php
public function boot() {
    if (app()->environment('production')) {
        URL::forceScheme('https');
    }
}

生产部署清单

  1. 环境配置

    • 设置 APP_ENV=production
    • 禁用 APP_DEBUG
    • 配置生产环境域名
  2. 安全加固

    • 全站启用 HTTPS
    • 配置认证接口速率限制
    • 设置严格的 CORS 策略
  3. 性能优化

    • 使用 Redis 缓存会话
    • 配置会话垃圾回收
    • 监控认证失败率

故障排除流程

  1. 基础配置检查

    • Sanctum 是否正确安装
    • statefulApi() 中间件是否启用
    • 前端域名是否在白名单
  2. 请求头验证

    • 包含 Accept: application/json
    • Origin 标头与配置匹配
    • X-XSRF-TOKEN 存在于修改状态请求
  3. CSRF 流程

    • 认证前是否获取了 CSRF Cookie
    • 是否正确提取和传递令牌
  4. CORS 配置

    • supports_credentials 设为 true
    • 使用具体域名(禁用通配符 *)
  5. 会话管理

    • 会话驱动配置是否正确
    • Cookie 是否正确设置和传输

结语

Laravel Sanctum 的基于 Cookie 身份验证为 SPA 提供了强大而安全的解决方案。通过利用 HTTP-only Cookie 和 Laravel 内置会话管理,开发者可以实现既安全又高效的身份验证系统。

成功实施的关键在于深入理解完整流程:获取 CSRF 令牌、正确配置 CORS 以及确保请求头的一致性。结合适当的安全措施和测试,Sanctum 将成为现代 Web 应用不可或缺的工具。

参考资源