什么是 Sanctum
Laravel Sanctum 是面向 SPA(单页应用)、移动应用以及简单 API 的轻量认证包。无需复杂的 OAuth 知识,也能为每个用户发放并管理多个 API 令牌。
Sanctum 解决两大问题:
| 认证模式 | 机制 | 主要用途 |
|---|
| API 令牌认证 | Authorization: Bearer <token> 头 | 移动应用、第三方集成 |
| SPA 认证 | Session Cookie + CSRF 保护 | 自家前端(Vue / React 等) |
从自家 SPA 调用 API 时使用 SPA 认证。移动应用或第三方调用 API 时使用 API 令牌认证。也可以只使用其中一种。
与 Passport 的选择
| Sanctum | Passport |
|---|
| 复杂度 | 简单 | 完整 OAuth2 |
| 适合用途 | 自家 SPA、移动应用 | 面向外部应用的 OAuth 提供方 |
| 令牌类型 | 个人访问令牌 | OAuth2 访问令牌 |
如果需要作为 OAuth2 提供方对接外部服务,请选择 Passport;大多数应用使用 Sanctum 就够了。
安装与配置
只需执行 install:api Artisan 命令即可完成 Sanctum 的配置。
该命令会自动完成:
- 安装
laravel/sanctum 包
- 发布
personal_access_tokens 表的迁移文件
- 执行迁移
添加 HasApiTokens trait
在 User 模型上添加 HasApiTokens trait。
// app/Models/User.php
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable
{
use HasApiTokens, HasFactory, Notifiable;
}
这样就可以使用 $user->createToken()、$user->tokens 等方法。
API 令牌认证
令牌流程
签发令牌
通过 createToken() 签发令牌。可以通过 plainTextToken 属性获取明文令牌值。明文令牌不会保存到数据库,因此签发后必须立即返回给用户。
use Illuminate\Http\Request;
Route::post('/tokens/create', function (Request $request) {
$token = $request->user()->createToken($request->token_name);
return ['token' => $token->plainTextToken];
})->middleware('auth');
数据库中保存的是经 SHA-256 哈希后的令牌。
设置 Scope(能力)
可以为令牌附加能力(scope),限定该令牌能够执行的操作。
// 签发带 scope 的令牌
$token = $user->createToken('mobile-app', ['server:update', 'server:read']);
return $token->plainTextToken;
处理请求时检查令牌的 scope:
if ($request->user()->tokenCan('server:update')) {
// 执行更新操作
}
if ($request->user()->tokenCant('server:update')) {
abort(403);
}
通过中间件校验 scope
在 bootstrap/app.php 注册中间件别名:
use Laravel\Sanctum\Http\Middleware\CheckAbilities;
use Laravel\Sanctum\Http\Middleware\CheckForAnyAbility;
->withMiddleware(function (Middleware $middleware): void {
$middleware->alias([
'abilities' => CheckAbilities::class, // 拥有所有能力
'ability' => CheckForAnyAbility::class, // 拥有任一能力
]);
})
在路由上应用中间件:
// 只允许同时具备 check-status 和 place-orders 的令牌
Route::get('/orders', function () {
// ...
})->middleware(['auth:sanctum', 'abilities:check-status,place-orders']);
// 允许具备 check-status 或 place-orders 之一的令牌
Route::get('/orders', function () {
// ...
})->middleware(['auth:sanctum', 'ability:check-status,place-orders']);
令牌有效期
默认情况下 Sanctum 令牌没有有效期。可通过 config/sanctum.php 的 expiration 选项以分钟为单位设置。
// config/sanctum.php
'expiration' => 525600, // 365 天(分钟)
也可以按令牌指定有效期:
$token = $user->createToken(
'token-name',
['*'],
now()->addWeeks(1) // 1 周后过期
)->plainTextToken;
若设置了有效期,请定期清理过期令牌。
use Illuminate\Support\Facades\Schedule;
Schedule::command('sanctum:prune-expired --hours=24')->daily();
吊销令牌
// 删除全部令牌
$user->tokens()->delete();
// 删除当前请求所使用的令牌
$request->user()->currentAccessToken()->delete();
// 删除特定令牌
$user->tokens()->where('id', $tokenId)->delete();
SPA 认证
SPA 认证使用 Session Cookie,无需签发和管理令牌。适合自家前端(Vue、React、Next.js 等)调用 API 的场景。
使用 SPA 认证要求 SPA 与 API 共享同一顶级域名(子域名可以不同)。同时请求需包含 Accept: application/json 头和 Referer 或 Origin 头。
启用 Sanctum 中间件
在 bootstrap/app.php 中启用 statefulApi()。
->withMiddleware(function (Middleware $middleware): void {
$middleware->statefulApi();
})
配置一方(First-party)域名
在 config/sanctum.php 的 stateful 选项中配置 SPA 的域名。
// config/sanctum.php
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
'%s%s',
'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1',
Sanctum::currentApplicationUrlWithPort()
))),
CORS 配置
若从其他子域名调用 API,需要配置 CORS。
php artisan config:publish cors
在 config/cors.php 中将 supports_credentials 设置为 true。
// config/cors.php
'supports_credentials' => true,
前端 axios 也需配置:
// resources/js/bootstrap.js
axios.defaults.withCredentials = true;
axios.defaults.withXSRFToken = true;
别忘了设置 Session Cookie 域名:
// config/session.php
'domain' => '.example.com', // 前面加点
来自 SPA 的认证流程
获取 CSRF Cookie
登录前访问 /sanctum/csrf-cookie 初始化 CSRF 保护。await axios.get('/sanctum/csrf-cookie');
发送已认证请求
登录后的请求会通过 Session Cookie 自动认证。const response = await axios.get('/api/user');
console.log(response.data); // 已认证用户信息
保护已认证路由
在路由上应用 auth:sanctum 中间件,未认证的请求会返回 401 Unauthorized。API 令牌认证与 SPA 认证都由同一个中间件处理。
use Illuminate\Http\Request;
// 保护单一路由
Route::get('/user', function (Request $request) {
return $request->user();
})->middleware('auth:sanctum');
// 保护路由组
Route::middleware('auth:sanctum')->group(function () {
Route::get('/profile', [ProfileController::class, 'show']);
Route::put('/profile', [ProfileController::class, 'update']);
Route::get('/posts', [PostController::class, 'index']);
});
实用示例:登录 API 与返回令牌
下面是移动应用使用 API 令牌认证的示例。
创建登录端点
// routes/api.php
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\ValidationException;
Route::post('/sanctum/token', function (Request $request) {
$request->validate([
'email' => ['required', 'email'],
'password' => ['required'],
'device_name' => ['required', 'string'],
]);
$user = User::where('email', $request->email)->first();
if (! $user || ! Hash::check($request->password, $user->password)) {
throw ValidationException::withMessages([
'email' => ['提供的凭据不正确。'],
]);
}
return response()->json([
'token' => $user->createToken($request->device_name)->plainTextToken,
]);
});
创建已认证路由
// routes/api.php
Route::middleware('auth:sanctum')->group(function () {
// 返回当前用户信息
Route::get('/user', function (Request $request) {
return $request->user();
});
// 吊销当前令牌并登出
Route::post('/logout', function (Request $request) {
$request->user()->currentAccessToken()->delete();
return response()->json(['message' => '已登出。']);
});
// 登出所有设备
Route::post('/logout/all', function (Request $request) {
$request->user()->tokens()->delete();
return response()->json(['message' => '已从所有设备登出。']);
});
});
从客户端发起请求
// 登录
const { data } = await axios.post('/api/sanctum/token', {
email: '[email protected]',
password: 'password',
device_name: 'My iPhone',
});
const token = data.token;
// 已认证请求
const response = await axios.get('/api/user', {
headers: {
Authorization: `Bearer ${token}`,
},
});
在 Sanctum 测试中,使用 Sanctum::actingAs() 认证用户并指定要授予的能力。
use App\Models\User;
use Laravel\Sanctum\Sanctum;
test('可以获取任务列表', function () {
Sanctum::actingAs(
User::factory()->create(),
['view-tasks']
);
$response = $this->get('/api/tasks');
$response->assertOk();
});
test('拥有全部能力的令牌可访问', function () {
Sanctum::actingAs(
User::factory()->create(),
['*']
);
$response = $this->get('/api/tasks');
$response->assertOk();
});
use App\Models\User;
use Laravel\Sanctum\Sanctum;
public function test_task_list_can_be_retrieved(): void
{
Sanctum::actingAs(
User::factory()->create(),
['view-tasks']
);
$response = $this->get('/api/tasks');
$response->assertOk();
}
# 安装 Sanctum 并执行迁移
php artisan install:api
在 User 模型上添加 HasApiTokens trait:use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable
{
use HasApiTokens, HasFactory, Notifiable;
}
// 签发令牌
$token = $user->createToken('token-name')->plainTextToken;
// 签发带 scope 的令牌
$token = $user->createToken('token-name', ['read', 'write'])->plainTextToken;
// 检查能力
$user->tokenCan('read'); // true/false
$user->tokenCant('write'); // true/false
// 吊销令牌
$user->tokens()->delete(); // 全部令牌
$request->user()->currentAccessToken()->delete(); // 当前令牌
$user->tokens()->where('id', $id)->delete(); // 特定令牌
- API 令牌认证:适用于移动应用、第三方、CLI 工具等无 Session 的客户端。
- SPA 认证:适用于自家 Vue / React / Next.js 前端等同一域名(或子域名)上的 SPA。更安全,无需管理令牌。