Laravel Sanctum SPA 认证原理

解读

国内一线/二线互联网公司面试时,面试官问“Sanctum SPA 认证原理”并不是想听你背文档,而是想确认三件事:

  1. 你是否真的用 Sanctum 做过前后端分离项目,而不是只会 Passport;
  2. 你是否理解“为什么 Sanctum 不需要 OAuth 那套复杂流程就能保证安全”;
  3. 你是否能把“浏览器 SameSite、Laravel 中间件、API 令牌、CSRF”串成一条线,讲清楚攻击面与防御面。

因此,回答要围绕“Cookie 而非 Header”“First-Party SPA”“CSRF 双重校验”“令牌生命周期”四个关键词展开,用“请求链路”把原理讲透。

知识点

  1. First-Party SPA 场景定义:域名如 api.example.com 与 spa.example.com 属于同一主域,前端 JS 可以携带 Cookie 访问接口。
  2. Sanctum 暴露的两个路由:
    • /sanctum/csrf-cookie:返回 XSRF-TOKEN Cookie,供 Axios/Angular 自动写入请求头 X-XSRF-TOKEN;
    • /login:校验用户名密码后,生成“个人访问令牌”(Personal Access Token),写入数据库,同时把 token 值再次返回 Set-Cookie(laravel_session + sanctum 自身不额外再种 Cookie)。
  3. 后续请求认证流程:
    • 浏览器自动带上 laravel_session Cookie;
    • EnsureFrontendRequestsAreStateful 中间件校验 Cookie 中的 session_id 是否合法;
    • 若同时存在 Authorization: Bearer xxx 头,则优先用令牌,SPA 场景下通常无此头。
  4. 安全加固点:
    • Cookie 必须 Secure、HttpOnly、SameSite=Lax(Sanctum 默认);
    • 所有写操作路由必须再走一次 CSRF 校验(X-XSRF-TOKEN 头);
    • 令牌支持 abilities/scopes,可细粒度授权;
    • 支持在数据库中刷新 last_used_at,异常登录可快速吊销。
  5. 与 Passport 差异:Passport 走 OAuth2,需要 Client ID、Client Secret、Authorization Code,适合第三方;Sanctum 走 Session+Cookie,适合自有 SPA,性能高、实现轻。

答案

Laravel Sanctum 为“同站 SPA”设计了一套“Session + Cookie”的精简认证机制,核心流程分四步:

第一步,前端先访问 /sanctum/csrf-cookie,Laravel 返回 XSRF-TOKEN Cookie,并把令牌值同时写入响应体,前端框架(Axios 默认)会自动把该值带到后续请求的 X-XSRF-TOKEN 头。

第二步,用户提交账密到 /login,Laravel 校验通过后,在 sessions 表生成 session 记录,把 session_id 通过 Set-Cookie 种回浏览器(laravel_session),同时在 personal_access_tokens 表插入一条“个人访问令牌”,但 SPA 场景下并不把该令牌返回给前端,而是继续用 Cookie 维持会话。

第三步,前端每次调用 /api/* 接口,浏览器自动携带 laravel_session;Sanctum 的 EnsureFrontendRequestsAreStateful 中间件通过 Cookie 中的 session_id 还原用户身份,完成认证;若路由在 api 组里同时加了 auth:sanctum,则进一步确认 personal_access_tokens 表中存在对应令牌(session 与令牌通过 user_id 关联)。

第四步,对于 POST/PUT/PATCH/DELETE 请求,Laravel 的 VerifyCsrfToken 中间件会校验 X-XSRF-TOKEN 头,防止跨站伪造;由于 Cookie 已设置 SameSite=Lax 且接口返回 Access-Control-Allow-Credentials=true,CSRF 与 CORS 攻击面均被收敛。

整个过程中,前端无需手动在 Header 里塞 Authorization,令牌不落地浏览器本地存储,避免 XSS 泄露;后端仍保留 Laravel 传统的 Session 机制,Redis 或数据库存储即可水平扩展,兼顾安全与性能。

拓展思考

  1. 如果主域相同但子域不同(spa.example.com 调用 api.example.com),需要把 SESSION_DOMAIN 设为 .example.com,并在 cors.php 中支持 allowed_origins 与 supports_credentials,否则 Cookie 无法跨子域携带。
  2. 在高并发场景下,可把 session 存储从文件改为 Redis,并开启 phpredis 的 session locking=0,减少锁等待;同时利用 Sanctum 的 last_used_at 字段,定期清理冷令牌,避免表膨胀。
  3. 若后续需要开放给第三方小程序或 App,可在同一套用户表上再启用 Sanctum 的“令牌颁发模式”:用户登录后返回纯字符串 token,第三方自行存储到 Header,做到同一套代码兼容 First-Party SPA 与 Third-Party 客户端。
  4. 安全加固可再加一层“令牌绑定 IP / User-Agent”,并在个人中心提供“一键踢出所有设备”功能,利用 personal_access_tokens 的 id 批量删除,实现类似微信的账号安全控制台。