Sanctum 授权
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 并不意味着你必须同时用到它所提供的全部功能。
安装
你可以通过 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 的「范围」类似。您可以将字符串能力数组作为第二个参数传递给 createToken
方法:
return $user->createToken('token-name', ['server:update'])->plainTextToken;
在处理由 Sanctum 验证的传入请求时,您可以使用 tokenCan
方法确定令牌是否具有给定的能力:
if ($user->tokenCan('server:update')) {
//
}
第一方 UI 发起的请求
为方便起见,如果传入的经过身份验证的请求来自您的第一方 SPA,并且您正在使用 Sanctum 的内置 SPA 身份验证,则 tokenCan
方法将始终返回 true
。
但是,这并不一定意味着您的应用必须允许用户执行操作。通常,您的应用的 授权策略 将确定令牌是否已被授予执行能力的权限,并检查是否应允许用户实例本身来执行操作。
例如,假设我们有一个管理服务器的应用,那就要检查令牌是否有权更新服务器 和 服务器是否属于用户:
return $request->user()->id === $server->user_id &&
$request->user()->tokenCan('server:update')
起初,允许调用 tokenCan
方法并始终为第一方 UI 发起的请求返回 true
可能看起来很奇怪; 但是,能够始终假设 API 令牌可用并且可以通过 tokenCan
方法进行检查是很方便的。 通过采用这种方法,您可以始终在应用程序的授权策略中调用 tokenCan
方法,而无需担心请求是从应用程序的 UI 触发还是由 API 的第三方使用者之一发起。
保护路由
为了保护路由,所有传入请求都必须经过身份验证,您应该将 sanctum
身份验证看守器附加到 routes/web.php
和 routes/api.php
中的受保护路由。 如果请求来自第三方,此看守器将确保传入请求被验证为有状态的 cookie 验证请求或包含有效的 API 令牌标头。
您可能想知道为什么我们建议您使用 sanctum
看守器来验证应用程序的 routes/web.php
文件中的路由。 请记住,Sanctum 将首先尝试使用 Laravel 的典型 session 身份验证 cookie 对传入请求进行身份验证。 如果该 cookie 不存在,则 Sanctum 将尝试使用请求的 Authorization
标头中的令牌来验证请求。 此外,使用 Sanctum 对所有请求进行身份验证可确保我们始终可以在当前经过身份验证的用户实例上调用 tokenCan
方法:
use Illuminate\Http\Request;
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
return $request->user();
});
撤销令牌
您可以通过使用 Laravel\Sanctum\HasApiTokens
trait 提供的 tokens
关联关系从数据库中删除令牌,以达到「撤销」令牌的目的:
// 撤销所有令牌...
$user->tokens()->delete();
// 撤销用于验证当前请求的令牌...
$request->user()->currentAccessToken()->delete();
// 撤销指定令牌...
$user->tokens()->where('id', $tokenId)->delete();
SPA 认证
Sanctum 还提供了一种简单的方法来验证需要与 Laravel 支持的 API 通信的单页应用程序 (SPA)。 这些 SPA 可能与 Laravel 应用程序存在于同一个存储库中,也可能是一个完全独立的存储库。
对于此功能,Sanctum 不使用任何类型的令牌。 相反,Sanctum 使用 Laravel 内置的基于 cookie 的 session 身份验证服务。 这种身份验证方法提供了 CSRF 保护、session 身份验证以及防止身份验证凭据通过 XSS 泄漏的好处。
注意:为了完成认证,你的 SPA 与 API 必须共享同一个顶级域名。 但是,它们可以被放置在不同的子域名上。
配置
配置你的第一方域
首先,您应该配置您的 SPA 将从哪些域发出请求。 你可以使用 sanctum
配置文件中的 stateful
选项来配置这些域。 此配置设置确定哪些域将在向您的 API 发出请求时使用 Laravel session cookie 维护「有状态的」身份验证。
注意:如果您通过包含端口 (
127.0.0.1:8000
)的 URL 访问应用程序,则应确保在域中包含端口号。
Sanctum 中间件
接下来,您应该将 Sanctum 的中间件添加到您的 app/Http/Kernel.php
文件中的 api
中间件组中。 这个中间件负责确保来自 SPA 的传入请求可以使用 Laravel 的会话 cookie 进行身份验证,同时仍然允许来自第三方或移动应用程序的请求使用 API 令牌进行身份验证:
use Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful;
'api' => [
EnsureFrontendRequestsAreStateful::class,
'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
CORS & Cookies
如果您无法从在单独子域上执行的 SPA 对您的应用程序进行身份验证,则您可能错误配置了 CORS(跨源资源共享)或会话 cookie 设置。
您应该确保应用程序的 CORS 配置返回的 Access-Control-Allow-Credentials
标头的值为 true
。 这可以通过将应用程序的 config/cors.php
配置文件中的 supports_credentials
选项设置为 true
来实现。
此外,您应该在应用程序的全局 axios
实例上启用 withCredentials
选项。 通常,这应该在您的 resources/js/bootstrap.js
文件中执行。 如果你没有使用 Axios 从你的前端发出 HTTP 请求,你应该在你自己的 HTTP 客户端上执行等效的配置:
axios.defaults.withCredentials = true;
最后,您应该确保应用程序的会话 cookie 域配置支持根域的任何子域。 您可以通过在应用程序的 config/session.php
配置文件中使用前导 .
作为域的前缀来实现此目的:
'domain' => '.domain.com',
验证
CSRF 保护
要验证您的 SPA,您的 SPA 的 「登录」页面应首先向 /sanctum/csrf-cookie
发出请求以初始化应用程序的 CSRF 保护:
axios.get('/sanctum/csrf-cookie').then(response => {
// 登录...
});
在此请求期间,Laravel 将设置一个包含当前 CSRF 令牌的 XSRF-TOKEN
cookie。然后,此令牌应在后续请求的 X-XSRF-TOKEN
标头中传递,某些 HTTP 客户端库(如 Axios 和 Angular HttpClient)将自动为您执行此操作。如果您的 JavaScript HTTP 库没有为您设置该值,您将需要手动设置 X-XSRF-TOKEN
标头以匹配此路由设置的 XSRF-TOKEN
cookie 的值。
登录
一旦 CSRF 保护被初始化,你应该向 Laravel 应用程序的 /login
路由发出 POST
请求。这个 /login
路由可以 手动实现 或使用无标头身份验证包,如 Laravel Fortify。
如果登录请求成功,您将通过身份验证,随后对您的应用程序路由的请求将通过 Laravel 应用程序发布给您的客户端的会话 cookie 自动进行身份验证。此外,由于您的应用程序已经向 /sanctum/csrf-cookie
路由发出请求,只要您的 JavaScript HTTP 客户端发送 XSRF-TOKEN
cookie 的值,后续请求应该会自动接受 CSRF 保护 X-XSRF-TOKEN
标头。
当然,如果你的用户 session 由于缺乏活动而过期,后续对 Laravel 应用程序的请求可能会收到 401 或 419 HTTP 错误响应。在这种情况下,您应该将用户重定向到 SPA 的登录页面。
注意:您可以自由编写自己的
/login
端点;但是,您应该确保它使用标准的 Laravel 提供的基于 session 的身份验证服务 对用户进行身份验证。通常,这意味着使用 「web」身份验证看守器。
路由保护
为了保护路由,以便所有传入的请求都必须经过身份验证,您应该将 sanctum
身份验证看守器附加到 routes/api.php
文件中的 API 路由。此看守器将确保传入请求被验证为来自您的 SPA 的有状态的已验证请求,或者如果请求来自第三方,则包含有效的 API 令牌标头:
use Illuminate\Http\Request;
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
return $request->user();
});
授权私有广播频道
如果你的单页面应用需要通过 私有 / presence 广播频道 进行身份认证,你需要在你的 routes/api.php
文件中调用 Broadcast::routes
方法:
Broadcast::routes(['middleware' => ['auth:sanctum']]);
接下来,为了让 Pusher 的授权请求成功,你需要在初始化 Laravel Echo 时提供一个自定义的 Pusher authorizer
。这允许您的应用程序配置 Pusher 以使用 为跨域请求正确配置 的 axios
实例:
window.Echo = new Echo({
broadcaster: "pusher",
cluster: process.env.MIX_PUSHER_APP_CLUSTER,
encrypted: true,
key: process.env.MIX_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 令牌的方式上存在细微差别。
发行 API 令牌
首先,创建一个接受用户电子邮件/用户名、密码和设备名称的路由,然后将这些凭据交换为新的 Sanctum 令牌。赋予此端点的 “设备名称”仅供参考,可以是你希望的任何值。通常,设备名称值应该是用户可以识别的名称,例如 “Nuno’s 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 请求时,它应将令牌作为 Bearer
令牌传递到 Authorization
请求头中。
技巧:在为移动应用程序发行令牌时,你还可以自由指定 token abilities。
路由保护
如前所述,你需要保护路由,因此必须通过在路由上附加 Sanctum
身份验证看守器来对所有传入请求进行身份验证。
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
return $request->user();
});
撤销令牌
为了允许用户撤销发给移动设备的 API 令牌,您可以在 Web 应用程序 UI 的「帐户设置」部分中按名称列出它们,并附带 「撤销」按钮。 当用户点击「撤销」按钮时,您可以从数据库中删除令牌。 请记住,您可以通过 Laravel\Sanctum\HasApiTokens
trait 提供的 tokens
关系访问用户的 API 令牌:
// 撤销所有令牌...
$user->tokens()->delete();
// 撤销特定令牌...
$user->tokens()->where('id', $tokenId)->delete();
测试
在测试时,Sanctum::actingAs
方法可用于验证用户并指定为其令牌授予哪些能力:
use App\Models\User;
use Laravel\Sanctum\Sanctum;
public function test_task_list_can_be_retrieved()
{
Sanctum::actingAs(
User::factory()->create(),
['view-tasks']
);
$response = $this->get('/api/task');
$response->assertOk();
}
如果你想授予令牌所有的能力,你应该在提供给 actingAs
方法的能力列表中包含 *
:
Sanctum::actingAs(
User::factory()->create(),
['*']
);