Laravel Sanctum
介绍
Laravel Sanctum 为 SPA(单页应用程序)、移动应用程序和基于令牌的、简单的 API 提供轻量级身份验证系统。Sanctum 允许应用程序的每个用户为他们的帐户生成多个 API 令牌。这些令牌可以被授予指定允许令牌执行哪些操作的能力 / 范围。
工作原理
Laravel Sanctum 是为了解决两个独立问题而生。在深入研究之前,我们先来讨论一下。
API 令牌
首先,它是一个简单的包,用于向用户发出 API 令牌,而不涉及 OAuth。这个功能的灵感来自 GitHub 的「访问令牌」。例如,假设应用程序的「帐户设置」有一个界面,用户可以在其中为其帐户生成 API 令牌。你可以使用 Sanctum 来生成和管理这些令牌。这些令牌通常有很长的过期时间(以年计),当然用户可以随时手动将其撤销。
Laravel Sanctum 的这个特性是通过将用户 API 令牌存储在单个数据库表中,并通过包含了有效 API 令牌的 Authorization
标识头对传入的请求进行身份验证而实现的。
SPA 身份验证
其次,Sanctum 提供了一种简单的方法来认证需要与基于 Laravel 的 API 进行通信的单页应用程序 (SPAs)。这些 SPAs 可能与 Laravel 应用程序存在于同一仓库中,也可能是一个完全独立的仓库,例如使用 Vue CLI 或者 Next.js 创建的单页应用。
对于此功能,Sanctum 不使用任何类型的令牌。相反,Sanctum 使用 Laravel 内置的基于 cookie 的会话身份验证服务。这提供了 CSRF 保护,会话身份验证以及防止因 XSS 攻击而泄漏身份验证凭据。仅当传入请求来自您自己的 SPA 前端时,Sanctum 才会尝试使用 Cookie 进行身份验证。通常,Sanctum 利用 Laravel 的 web 身份验证保护来实现这一点。这提供了 CSRF 保护、会话身份验证以及防止通过 XSS 攻击而泄漏身份验证凭据。
Sanctum 处理你自己的 SPA 前端的请求时,只会尝试使用 cookie 进行身份验证。当 Sanctum 检查传入的 HTTP 请求时,它将首先检查验证身份的 cookie,如果不存在,Sanctum 将检查 Authorization
标识头以获取有效的 API 令牌。
技巧:仅将 Sanctum 用于API令牌身份验证或仅用于 SPA 身份验证也是完全可以的。因为你使用 Sanctum 并不意味着你必须同时使用它提供的两种功能。
安装
技巧:最新版本的 Laravel 已经包含了 Laravel Sanctum,但是,如果您的应用程序中 composer.json 文件里不包含 "laravel/sanctum"
的话,您可以按照下面的说明进行安装。
您可以通过Composer软件包管理器安装Laravel Sanctum:
composer require laravel/sanctum
接下来,你需要使用 vendor:publish
Artisan 命令发布 Sanctum 的配置和迁移文件。Sanctum 的配置文件将会保存在 config
文件夹中:
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
最后,您应该运行数据库迁移。 Sanctum 将创建一个数据库表来存储 API 令牌:
php artisan migrate
接下来,如果您想利用 Sanctum 对 SPA 进行身份验证,您应该将 Sanctum 的中间件添加到您应用的 app/Http/Kernel.php
文件中的 api
中间件组中:
'api' => [
\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
自定义迁移
如果你不想使用 Sanctum 的默认迁移,你应该在 App\Providers\AppServiceProvider
类的 register 方法中调用 Sanctum::ignoreMigrations
方法。 您可以通过执行以下命令导出默认迁移:php artisan vendor:publish --tag=sanctum-migrations
配置
重写默认模型
尽管通常不需要,但您可以自由扩展 Sanctum 内部使用的 PersonalAccessToken
模型:
use Laravel\Sanctum\PersonalAccessToken as SanctumPersonalAccessToken;
class PersonalAccessToken extends SanctumPersonalAccessToken
{
// ...
}
然后,您可以通过 Sanctum 提供的 usePersonalAccessTokenModel
方法指示 Sanctum 使用您的自定义模型。 通常,您应该在应用程序的服务提供器的 boot
方法中调用此方法:
use App\Models\Sanctum\PersonalAccessToken;
use Laravel\Sanctum\Sanctum;
/**
* 引导应用程序服务。
*
* @return void
*/
public function boot()
{
Sanctum::usePersonalAccessTokenModel(PersonalAccessToken::class);
}
API 令牌认证
技巧:你不应使用 API 令牌来验证自己的第一方 SPA。 而应使用 Sanctum 的内置 SPA 身份验证功能。
发布 API Tokens
Sanctum 允许你发布 API 令牌/个人访问令牌, 用于对你的应用程序的 API 请求进行身份验证。 使用 API 令牌发出请求时,令牌应作为 Bearer
令牌包含在 Authorization
请求头中。
要开始为用户颁发令牌,你的 User 模型应使用 Laravel\Sanctum\HasApiTokens
trait:
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable
{
use HasApiTokens, HasFactory, Notifiable;
}
要发布令牌,你可以使用 createToken
方法。 createToken
方法返回一个 Laravel\Sanctum\NewAccessToken
实例。 在存入数据库之前,API 令牌已使用 SHA-256 哈希加密过,但你可以使用 NewAccessToken
实例的 plainTextToken
属性访问令牌的纯文本值。创建令牌后,你应该立即向用户显示此值:
use Illuminate\Http\Request;
Route::post('/tokens/create', function (Request $request) {
$token = $request->user()->createToken($request->token_name);
return ['token' => $token->plainTextToken];
});
你可以使用 HasApiTokens
trait 提供的 tokens
Eloquent 关系访问用户的所有令牌:
foreach ($user->tokens as $token) {
//
}
令牌能力
Sanctum 允许你将 「能力」分配给令牌。能力的用途与 OAuth 的「Scope」类似。你可以将字符串能力数组作为第二个参数传递给 createToken
方法:
return $user->createToken('token-name', ['server:update'])->plainTextToken;
在处理由 Sanctum 验证的传入请求时,你可以使用 tokenCan
方法确定令牌是否具有给定的能力:
if ($user->tokenCan('server:update')) {
//
}
令牌能力中间件
Sanctum 还包括两个中间件,可用于验证传入请求是否使用已被授予给定能力的令牌进行身份验证。首先,将以下中间件添加到应用程序的 app/Http/Kernel.php
文件的 $routeMiddleware
属性中:
'abilities' => \Laravel\Sanctum\Http\Middleware\CheckAbilities::class,
'ability' => \Laravel\Sanctum\Http\Middleware\CheckForAnyAbility::class,
abilities
中间件可以分配给一个路由,以验证传入请求的令牌是否具有所有列出的能力:
Route::get('/orders', function () {
// Token has both "check-status" and "place-orders" abilities...
})->middleware(['auth:sanctum', 'abilities:check-status,place-orders']);
ability
中间件可以分配给一个路由,以验证传入请求的令牌是否具有至少一个列出的能力:
Route::get('/orders', function () {
// Token has the "check-status" or "place-orders" ability...
})->middleware(['auth:sanctum', 'ability:check-status,place-orders']);