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