Laravel Sanctum
简介
Laravel Sanctum 为 SPA(单页应用程序)、移动应用程序和简单的基于令牌的 API 提供了一个轻量级的身份验证系统。 Sanctum 允许您的应用程序的每个用户为其帐户生成多个 API 令牌。 这些令牌可以被授予能力/作用域,以指定令牌允许执行哪些操作。
工作原理
Laravel Sanctum 的存在是为了解决两个不同的问题。 在深入研究库之前,让我们先讨论一下每个问题。
API 令牌
首先,Sanctum 是一个简单的软件包,您可以使用它向用户颁发 API 令牌,而无需 OAuth 的复杂性。 此功能的灵感来自 GitHub 和其他颁发“个人访问令牌”的应用程序。 例如,想象一下您应用程序的“帐户设置”有一个屏幕,用户可以在其中为其帐户生成 API 令牌。 您可以使用 Sanctum 生成和管理这些令牌。 这些令牌通常具有非常长的到期时间(数年),但用户可以随时手动撤销。
Laravel Sanctum 通过在单个数据库表中存储用户 API 令牌,并通过应包含有效 API 令牌的 Authorization
标头验证传入的 HTTP 请求,从而提供此功能。
SPA 身份验证
其次,Sanctum 的存在是为了提供一种简单的方法来验证需要与 Laravel 驱动的 API 通信的单页应用程序 (SPA) 的身份。 这些 SPA 可能与您的 Laravel 应用程序存在于同一存储库中,或者可能是完全独立的存储库,例如使用 Next.js 或 Nuxt 创建的 SPA。
对于此功能,Sanctum 不使用任何类型的令牌。 相反,Sanctum 使用 Laravel 内置的基于 Cookie 的会话身份验证服务。 通常,Sanctum 利用 Laravel 的 web
身份验证守卫来实现此目的。 这提供了 CSRF 保护、会话身份验证的好处,并防止通过 XSS 泄露身份验证凭据。
仅当传入请求来自您自己的 SPA 前端时,Sanctum 才会尝试使用 Cookie 进行身份验证。 当 Sanctum 检查传入的 HTTP 请求时,它将首先检查身份验证 Cookie,如果不存在,Sanctum 然后将检查 Authorization
标头以查找有效的 API 令牌。
仅将 Sanctum 用于 API 令牌身份验证或仅用于 SPA 身份验证是完全可以的。 仅仅因为您使用 Sanctum 并不意味着您必须使用它提供的两个功能。
安装
您可以通过 install:api
Artisan 命令安装 Laravel Sanctum
1php artisan install:api
接下来,如果您计划使用 Sanctum 对 SPA 进行身份验证,请参阅本文档的 SPA 身份验证 部分。
配置
覆盖默认模型
虽然通常不是必需的,但您可以自由扩展 Sanctum 内部使用的 PersonalAccessToken
模型
1use Laravel\Sanctum\PersonalAccessToken as SanctumPersonalAccessToken;2 3class PersonalAccessToken extends SanctumPersonalAccessToken4{5 // ...6}
然后,您可以指示 Sanctum 通过 Sanctum 提供的 usePersonalAccessTokenModel
方法使用您的自定义模型。 通常,您应该在应用程序的 AppServiceProvider
文件的 boot
方法中调用此方法
1use App\Models\Sanctum\PersonalAccessToken; 2use Laravel\Sanctum\Sanctum; 3 4/** 5 * Bootstrap any application services. 6 */ 7public function boot(): void 8{ 9 Sanctum::usePersonalAccessTokenModel(PersonalAccessToken::class);10}
API 令牌身份验证
您不应使用 API 令牌来验证您自己的第一方 SPA 的身份。 而是使用 Sanctum 内置的 SPA 身份验证功能。
颁发 API 令牌
Sanctum 允许您颁发 API 令牌/个人访问令牌,这些令牌可用于验证对您的应用程序的 API 请求的身份。 使用 API 令牌发出请求时,令牌应包含在 Authorization
标头中,作为 Bearer
令牌。
要开始为用户颁发令牌,您的 User 模型应使用 Laravel\Sanctum\HasApiTokens
trait
1use Laravel\Sanctum\HasApiTokens;2 3class User extends Authenticatable4{5 use HasApiTokens, HasFactory, Notifiable;6}
要颁发令牌,您可以使用 createToken
方法。 createToken
方法返回 Laravel\Sanctum\NewAccessToken
实例。 API 令牌在使用 SHA-256 哈希处理后存储在您的数据库中,但您可以使用 NewAccessToken
实例的 plainTextToken
属性访问令牌的纯文本值。 您应该在创建令牌后立即向用户显示此值
1use Illuminate\Http\Request;2 3Route::post('/tokens/create', function (Request $request) {4 $token = $request->user()->createToken($request->token_name);5 6 return ['token' => $token->plainTextToken];7});
您可以使用 HasApiTokens
trait 提供的 tokens
Eloquent 关系访问用户的所有令牌
1foreach ($user->tokens as $token) {2 // ...3}
令牌能力
Sanctum 允许您为令牌分配“能力”。 能力的作用与 OAuth 的“作用域”类似。 您可以将字符串能力数组作为第二个参数传递给 createToken
方法
1return $user->createToken('token-name', ['server:update'])->plainTextToken;
在处理由 Sanctum 验证身份的传入请求时,您可以使用 tokenCan
或 tokenCant
方法确定令牌是否具有给定的能力
1if ($user->tokenCan('server:update')) {2 // ...3}4 5if ($user->tokenCant('server:update')) {6 // ...7}
令牌能力中间件
Sanctum 还包括两个中间件,可用于验证传入请求是否已使用已授予给定能力的令牌进行身份验证。 要开始使用,请在应用程序的 bootstrap/app.php
文件中定义以下中间件别名
1use Laravel\Sanctum\Http\Middleware\CheckAbilities;2use Laravel\Sanctum\Http\Middleware\CheckForAnyAbility;3 4->withMiddleware(function (Middleware $middleware) {5 $middleware->alias([6 'abilities' => CheckAbilities::class,7 'ability' => CheckForAnyAbility::class,8 ]);9})
可以将 abilities
中间件分配给路由,以验证传入请求的令牌是否具有所有列出的能力
1Route::get('/orders', function () {2 // Token has both "check-status" and "place-orders" abilities...3})->middleware(['auth:sanctum', 'abilities:check-status,place-orders']);
可以将 ability
中间件分配给路由,以验证传入请求的令牌是否具有至少一个列出的能力
1Route::get('/orders', function () {2 // Token has the "check-status" or "place-orders" ability...3})->middleware(['auth:sanctum', 'ability:check-status,place-orders']);
第一方 UI 发起的请求
为方便起见,如果传入的身份验证请求来自您的第一方 SPA 并且您正在使用 Sanctum 的内置 SPA 身份验证,则 tokenCan
方法将始终返回 true
。
但是,这并不一定意味着您的应用程序必须允许用户执行该操作。 通常,您的应用程序的授权策略将确定令牌是否已被授予执行这些能力的权限,并检查是否应允许用户实例本身执行该操作。
例如,如果我们想象一个管理服务器的应用程序,这可能意味着检查令牌是否被授权更新服务器并且服务器是否属于用户
1return $request->user()->id === $server->user_id &&2 $request->user()->tokenCan('server:update')
起初,允许调用 tokenCan
方法并始终为第一方 UI 发起的请求返回 true
似乎很奇怪; 但是,能够始终假设 API 令牌可用并且可以通过 tokenCan
方法进行检查是很方便的。 通过采用这种方法,您始终可以在应用程序的授权策略中调用 tokenCan
方法,而无需担心请求是从应用程序的 UI 触发还是由 API 的第三方使用者发起的。
保护路由
为了保护路由,以便所有传入请求都必须经过身份验证,您应该将 sanctum
身份验证守卫附加到 routes/web.php
和 routes/api.php
路由文件中的受保护路由。 此守卫将确保传入请求被验证为有状态的、Cookie 身份验证的请求,或者如果请求来自第三方,则包含有效的 API 令牌标头。
您可能想知道为什么我们建议您在应用程序的 routes/web.php
文件中使用 sanctum
守卫来验证路由。请记住,Sanctum 将首先尝试使用 Laravel 的典型会话身份验证 cookie 来验证传入的请求。如果该 cookie 不存在,则 Sanctum 将尝试使用请求的 Authorization
标头中的令牌来验证请求。此外,使用 Sanctum 验证所有请求可确保我们始终可以在当前经过身份验证的用户实例上调用 tokenCan
方法
1use Illuminate\Http\Request;2 3Route::get('/user', function (Request $request) {4 return $request->user();5})->middleware('auth:sanctum');
撤销令牌
您可以使用 Laravel\Sanctum\HasApiTokens
trait 提供的 tokens
关系,通过从数据库中删除令牌来“撤销”令牌
1// Revoke all tokens...2$user->tokens()->delete();3 4// Revoke the token that was used to authenticate the current request...5$request->user()->currentAccessToken()->delete();6 7// Revoke a specific token...8$user->tokens()->where('id', $tokenId)->delete();
令牌过期
默认情况下,Sanctum 令牌永不过期,只能通过撤销令牌来使其失效。但是,如果您想为应用程序的 API 令牌配置过期时间,您可以通过应用程序 sanctum
配置文件中定义的 expiration
配置选项来完成。此配置选项定义了颁发的令牌将被视为过期的分钟数
1'expiration' => 525600,
如果您想单独指定每个令牌的过期时间,您可以通过将过期时间作为第三个参数提供给 createToken
方法来完成
1return $user->createToken(2 'token-name', ['*'], now()->addWeek()3)->plainTextToken;
如果您为应用程序配置了令牌过期时间,您可能还希望计划一个任务来清理应用程序中过期的令牌。值得庆幸的是,Sanctum 包含一个 sanctum:prune-expired
Artisan 命令,您可以使用它来完成此操作。例如,您可以配置一个计划任务,以删除所有已过期至少 24 小时的过期令牌数据库记录
1use Illuminate\Support\Facades\Schedule;2 3Schedule::command('sanctum:prune-expired --hours=24')->daily();
SPA 身份验证
Sanctum 的存在也是为了提供一种简单的方法来验证需要与 Laravel 驱动的 API 通信的单页应用程序 (SPA)。这些 SPA 可能与您的 Laravel 应用程序位于同一存储库中,也可能是一个完全独立的存储库。
对于此功能,Sanctum 不使用任何类型的令牌。相反,Sanctum 使用 Laravel 内置的基于 cookie 的会话身份验证服务。这种身份验证方法提供了 CSRF 保护、会话身份验证的好处,并防止了通过 XSS 泄露身份验证凭据。
为了进行身份验证,您的 SPA 和 API 必须共享同一个顶级域名。但是,它们可以位于不同的子域上。此外,您应确保在请求中发送 Accept: application/json
标头以及 Referer
或 Origin
标头。
配置
配置您的第一方域名
首先,您应该配置您的 SPA 将从中发出请求的域名。您可以使用 sanctum
配置文件中的 stateful
配置选项来配置这些域名。此配置设置确定哪些域名在使用 Laravel 会话 cookie 向您的 API 发出请求时将保持“有状态”身份验证。
如果您通过包含端口的 URL (127.0.0.1:8000
) 访问您的应用程序,您应确保在域名中包含端口号。
Sanctum 中间件
接下来,您应该指示 Laravel 来自您的 SPA 的传入请求可以使用 Laravel 的会话 cookie 进行身份验证,同时仍然允许来自第三方或移动应用程序的请求使用 API 令牌进行身份验证。这可以通过在应用程序的 bootstrap/app.php
文件中调用 statefulApi
中间件方法轻松完成
1->withMiddleware(function (Middleware $middleware) {2 $middleware->statefulApi();3})
CORS 和 Cookie
如果您在从在单独子域上执行的 SPA 验证您的应用程序时遇到问题,则可能是您错误地配置了 CORS(跨域资源共享)或会话 cookie 设置。
默认情况下,不会发布 config/cors.php
配置文件。如果您需要自定义 Laravel 的 CORS 选项,您应该使用 config:publish
Artisan 命令发布完整的 cors
配置文件
1php artisan config:publish cors
接下来,您应确保应用程序的 CORS 配置返回 Access-Control-Allow-Credentials
标头,其值为 True
。这可以通过将应用程序 config/cors.php
配置文件中的 supports_credentials
选项设置为 true
来完成。
此外,您应该在应用程序的全局 axios
实例上启用 withCredentials
和 withXSRFToken
选项。通常,这应在您的 resources/js/bootstrap.js
文件中执行。如果您未使用 Axios 从前端发出 HTTP 请求,则应在您自己的 HTTP 客户端上执行等效的配置
1axios.defaults.withCredentials = true;2axios.defaults.withXSRFToken = true;
最后,您应确保应用程序的会话 cookie 域配置支持根域的任何子域。您可以通过在应用程序的 config/session.php
配置文件中以 .
为前缀来完成此操作
1'domain' => '.domain.com',
身份验证
CSRF 保护
为了验证您的 SPA,您的 SPA 的“登录”页面应首先向 /sanctum/csrf-cookie
端点发出请求,以初始化应用程序的 CSRF 保护
1axios.get('/sanctum/csrf-cookie').then(response => {2 // Login...3});
在此请求期间,Laravel 将设置一个包含当前 CSRF 令牌的 XSRF-TOKEN
cookie。然后,此令牌应进行 URL 解码,并在后续请求中以 X-XSRF-TOKEN
标头传递,某些 HTTP 客户端库(如 Axios 和 Angular HttpClient)将自动为您执行此操作。如果您的 JavaScript HTTP 库未为您设置该值,则您需要手动将 X-XSRF-TOKEN
标头设置为与此路由设置的 XSRF-TOKEN
cookie 的 URL 解码值匹配。
登录
初始化 CSRF 保护后,您应向 Laravel 应用程序的 /login
路由发出 POST
请求。此 /login
路由可以手动实现,也可以使用无头身份验证包(如 Laravel Fortify)实现。
如果登录请求成功,您将通过 Laravel 应用程序颁发给客户端的会话 cookie 自动验证身份,并且后续对应用程序路由的请求也将自动通过身份验证。此外,由于您的应用程序已向 /sanctum/csrf-cookie
路由发出请求,因此只要您的 JavaScript HTTP 客户端在 X-XSRF-TOKEN
标头中发送 XSRF-TOKEN
cookie 的值,后续请求应自动接收 CSRF 保护。
当然,如果用户的会话由于缺少活动而过期,则后续对 Laravel 应用程序的请求可能会收到 401 或 419 HTTP 错误响应。在这种情况下,您应将用户重定向到 SPA 的登录页面。
您可以自由编写自己的 /login
端点;但是,您应确保它使用标准的、Laravel 提供的基于会话的身份验证服务来验证用户身份。通常,这意味着使用 web
身份验证守卫。
保护路由
为了保护路由,以便所有传入的请求都必须经过身份验证,您应将 sanctum
身份验证守卫附加到 routes/api.php
文件中的 API 路由。此守卫将确保传入的请求要么是来自 SPA 的有状态身份验证请求,要么包含有效的 API 令牌标头(如果请求来自第三方)
1use Illuminate\Http\Request;2 3Route::get('/user', function (Request $request) {4 return $request->user();5})->middleware('auth:sanctum');
授权私有广播频道
如果您的 SPA 需要使用 私有/存在广播频道进行身份验证,您应从应用程序 bootstrap/app.php
文件中包含的 withRouting
方法中删除 channels
条目。相反,您应该调用 withBroadcasting
方法,以便您可以为应用程序的广播路由指定正确中间件
1return Application::configure(basePath: dirname(__DIR__))2 ->withRouting(3 web: __DIR__.'/../routes/web.php',4 // ...5 )6 ->withBroadcasting(7 __DIR__.'/../routes/channels.php',8 ['prefix' => 'api', 'middleware' => ['api', 'auth:sanctum']],9 )
接下来,为了使 Pusher 的授权请求成功,您需要在初始化 Laravel Echo 时提供自定义 Pusher authorizer
。这允许您的应用程序配置 Pusher 以使用 为跨域请求正确配置的 axios
实例
1window.Echo = new Echo({ 2 broadcaster: "pusher", 3 cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER, 4 encrypted: true, 5 key: import.meta.env.VITE_PUSHER_APP_KEY, 6 authorizer: (channel, options) => { 7 return { 8 authorize: (socketId, callback) => { 9 axios.post('/api/broadcasting/auth', {10 socket_id: socketId,11 channel_name: channel.name12 })13 .then(response => {14 callback(false, response.data);15 })16 .catch(error => {17 callback(true, error);18 });19 }20 };21 },22})
移动应用身份验证
您还可以使用 Sanctum 令牌来验证您的移动应用程序对您的 API 的请求。验证移动应用程序请求的过程类似于验证第三方 API 请求的过程;但是,在颁发 API 令牌的方式上存在细微差异。
颁发 API 令牌
首先,创建一个路由,该路由接受用户的电子邮件/用户名、密码和设备名称,然后将这些凭据交换为新的 Sanctum 令牌。提供给此端点的“设备名称”仅供参考,可以是您希望的任何值。一般来说,设备名称值应为用户可以识别的名称,例如“Nuno 的 iPhone 12”。
通常,您将从移动应用程序的“登录”屏幕向令牌端点发出请求。该端点将返回纯文本 API 令牌,然后可以将其存储在移动设备上,并用于发出其他 API 请求
1use App\Models\User; 2use Illuminate\Http\Request; 3use Illuminate\Support\Facades\Hash; 4use Illuminate\Validation\ValidationException; 5 6Route::post('/sanctum/token', function (Request $request) { 7 $request->validate([ 8 'email' => 'required|email', 9 'password' => 'required',10 'device_name' => 'required',11 ]);12 13 $user = User::where('email', $request->email)->first();14 15 if (! $user || ! Hash::check($request->password, $user->password)) {16 throw ValidationException::withMessages([17 'email' => ['The provided credentials are incorrect.'],18 ]);19 }20 21 return $user->createToken($request->device_name)->plainTextToken;22});
当移动应用程序使用令牌向您的应用程序发出 API 请求时,它应在 Authorization
标头中以 Bearer
令牌的形式传递令牌。
在为移动应用程序颁发令牌时,您还可以自由指定令牌能力。
保护路由
如前所述,您可以保护路由,以便通过将 sanctum
身份验证守卫附加到路由来强制所有传入的请求都必须经过身份验证
1Route::get('/user', function (Request $request) {2 return $request->user();3})->middleware('auth:sanctum');
撤销令牌
为了允许用户撤销颁发给移动设备的 API 令牌,您可以在 Web 应用程序 UI 的“帐户设置”部分中按名称列出它们,以及“撤销”按钮。当用户单击“撤销”按钮时,您可以从数据库中删除令牌。请记住,您可以通过 Laravel\Sanctum\HasApiTokens
trait 提供的 tokens
关系访问用户的 API 令牌
1// Revoke all tokens...2$user->tokens()->delete();3 4// Revoke a specific token...5$user->tokens()->where('id', $tokenId)->delete();
测试
在测试时,可以使用 Sanctum::actingAs
方法来验证用户身份并指定应授予其令牌的能力
1use App\Models\User; 2use Laravel\Sanctum\Sanctum; 3 4test('task list can be retrieved', function () { 5 Sanctum::actingAs( 6 User::factory()->create(), 7 ['view-tasks'] 8 ); 9 10 $response = $this->get('/api/task');11 12 $response->assertOk();13});
1use App\Models\User; 2use Laravel\Sanctum\Sanctum; 3 4public function test_task_list_can_be_retrieved(): void 5{ 6 Sanctum::actingAs( 7 User::factory()->create(), 8 ['view-tasks'] 9 );10 11 $response = $this->get('/api/task');12 13 $response->assertOk();14}
如果您想向令牌授予所有能力,您应在提供给 actingAs
方法的能力列表中包含 *
1Sanctum::actingAs(2 User::factory()->create(),3 ['*']4);