Laravel Sanctum
介绍
Laravel Sanctum 为 SPA(单页应用)、移动应用和简单的基于令牌的 API 提供了一个轻量级的认证系统。Sanctum 允许应用的每个用户为其账户生成多个 API 令牌。这些令牌可以被授予能力/范围,以指定令牌允许执行的操作。
工作原理
Laravel Sanctum 旨在解决两个独立的问题。
API 令牌
首先,它是一个简单的包,用于向用户颁发 API 令牌,而无需 OAuth 的复杂性。此功能的灵感来自 GitHub 的“访问令牌”。例如,想象一下应用的“账户设置”中有一个屏幕,用户可以为其账户生成 API 令牌。您可以使用 Sanctum 来生成和管理这些令牌。这些令牌通常具有很长的过期时间(几年),但用户可以随时手动撤销。
Laravel Sanctum 通过在单个数据库表中存储用户 API 令牌并通过 Authorization
头验证传入请求来提供此功能,该头应包含有效的 API 令牌。
SPA 认证
其次,Sanctum 提供了一种简单的方法来认证需要与 Laravel 驱动的 API 通信的单页应用(SPA)。这些 SPA 可能存在于与 Laravel 应用相同的存储库中,也可能是一个完全独立的存储库,例如使用 Vue CLI 创建的 SPA。
对于此功能,Sanctum 不使用任何类型的令牌。相反,Sanctum 使用 Laravel 内置的基于 cookie 的会话认证服务。这提供了 CSRF 保护、会话认证的好处,并防止通过 XSS 泄露认证凭据。Sanctum 仅在传入请求来自您自己的 SPA 前端时尝试使用 cookie 进行认证。
仅使用 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
中间件组中:
use Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful;
'api' => [
EnsureFrontendRequestsAreStateful::class,
'throttle:60,1',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
迁移自定义
如果您不打算使用 Sanctum 的默认迁移,您应该在 AppServiceProvider
的 register
方法中调用 Sanctum::ignoreMigrations
方法。您可以使用 php artisan vendor:publish --tag=sanctum-migrations
导出默认迁移。
API 令牌认证
您不应使用 API 令牌来认证您自己的第一方 SPA。相反,请使用 Sanctum 的内置 SPA 认证。
颁发 API 令牌
Sanctum 允许您颁发可用于认证 API 请求的 API 令牌/个人访问令牌。使用 API 令牌进行请求时,令牌应作为 Bearer
令牌包含在 Authorization
头中。
要开始为用户颁发令牌,您的用户模型应使用 HasApiTokens
trait:
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable
{
use HasApiTokens, Notifiable;
}
要颁发令牌,您可以使用 createToken
方法。createToken
方法返回一个 Laravel\Sanctum\NewAccessToken
实例。API 令牌在存储到数据库之前使用 SHA-256 哈希进行哈希处理,但您可以使用 NewAccessToken
实例的 plainTextToken
属性访问令牌的纯文本值。您应在令牌创建后立即将此值显示给用户:
$token = $user->createToken('token-name');
return $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')) {
//
}
为方便起见,如果传入的认证请求来自您的第一方 SPA 并且您正在使用 Sanctum 的内置 SPA 认证,则 tokenCan
方法将始终返回 true
。
保护路由
要保护路由以便所有传入请求都必须经过认证,您应该将 sanctum
认证守卫附加到 routes/api.php
文件中的 API 路由。此守卫将确保传入请求被认证为来自您的 SPA 的有状态认证请求,或者如果请求来自第三方,则包含有效的 API 令牌头:
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
return $request->user();
});
撤销令牌
您可以通过使用 HasApiTokens
trait 提供的 tokens
关系从数据库中删除令牌来“撤销”令牌:
// 撤销所有令牌...
$user->tokens()->delete();
// 撤销用户的当前令牌...
$request->user()->currentAccessToken()->delete();
// 撤销特定令牌...
$user->tokens()->where('id', $id)->delete();
SPA 认证
Sanctum 提供了一种简单的方法来认证需要与 Laravel 驱动的 API 通信的单页应用(SPA)。这些 SPA 可能存在于与 Laravel 应用相同的存储库中,也可能是一个完全独立的存储库,例如使用 Vue CLI 创建的 SPA。
对于此功能,Sanctum 不使用任何类型的令牌。相反,Sanctum 使用 Laravel 内置的基于 cookie 的会话认证服务。这提供了 CSRF 保护、会话认证的好处,并防止通过 XSS 泄露认证凭据。Sanctum 仅在传入请求来自您自己的 SPA 前端时尝试使用 cookie 进行认证。
为了进行认证,您的 SPA 和 API 必须共享相同的顶级域名。然而,它们可以放置在不同的子域上。
配置
配置您的第一方域
首先,您应该配置您的 SPA 将从哪些域发出请求。您可以使用 sanctum
配置文件中的 stateful
配置选项来配置这些域。此配置设置确定哪些域在向您的 API 发出请求时将使用 Laravel 会话 cookie 进行“有状态”认证。
如果您通过包含端口的 URL(如 127.0.0.1:8000
)访问您的应用,您应该确保在域中包含端口号。
Sanctum 中间件
接下来,您应该将 Sanctum 的中间件添加到 app/Http/Kernel.php
文件中的 api
中间件组中。此中间件负责确保来自您的 SPA 的传入请求可以使用 Laravel 的会话 cookie 进行认证,同时仍然允许来自第三方或移动应用的请求使用 API 令牌进行认证:
use Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful;
'api' => [
EnsureFrontendRequestsAreStateful::class,
'throttle:60,1',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
CORS 和 Cookies
如果您在从一个单独的子域执行的 SPA 中认证您的应用时遇到问题,您可能错误配置了 CORS(跨域资源共享)或会话 cookie 设置。
您应该确保您的应用的 CORS 配置返回 Access-Control-Allow-Credentials
头,其值为 True
,通过在应用的 cors
配置文件中将 supports_credentials
选项设置为 true
。
此外,您应该在全局 axios
实例上启用 withCredentials
选项。通常,这应该在 resources/js/bootstrap.js
文件中执行:
axios.defaults.withCredentials = true;
最后,您应该确保应用的会话 cookie 域配置支持根域的任何子域。您可以通过在 session
配置文件中在域前加上一个前导 .
来实现这一点:
'domain' => '.domain.com',
认证
要认证您的 SPA,您的 SPA 的登录页面应首先向 /sanctum/csrf-cookie
路由发出请求,以初始化应用的 CSRF 保护:
axios.get('/sanctum/csrf-cookie').then(response => {
// 登录...
});
在此请求期间,Laravel 将设置一个包含当前 CSRF 令牌的 XSRF-TOKEN
cookie。然后,此令牌应在后续请求中通过 X-XSRF-TOKEN
头传递,像 Axios 和 Angular HttpClient 这样的库会自动为您执行此操作。
一旦 CSRF 保护已初始化,您应该向典型的 Laravel /login
路由发出 POST
请求。此 /login
路由可能由 laravel/ui
认证脚手架 包提供。
如果登录请求成功,您将被认证,并且对您的 API 路由的后续请求将通过 Laravel 后端向客户端发出的会话 cookie 自动进行认证。
您可以自由编写自己的 /login
端点;然而,您应该确保它使用 Laravel 提供的标准基于会话的认证服务来认证用户。
保护路由
要保护路由以便所有传入请求都必须经过认证,您应该将 sanctum
认证守卫附加到 routes/api.php
文件中的 API 路由。此守卫将确保传入请求被认证为来自您的 SPA 的有状态认证请求,或者如果请求来自第三方,则包含有效的 API 令牌头:
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
return $request->user();
});
授权私有广播频道
如果您的 SPA 需要与私有/存在广播频道进行认证,您应该将 Broadcast::routes
方法调用放在 routes/api.php
文件中:
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 令牌。该端点将返回纯文本的 Sanctum 令牌,然后可以在移动设备上存储并用于发出其他 API 请求:
use App\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' => ['提供的凭据不正确。'],
]);
}
return $user->createToken($request->device_name)->plainTextToken;
});
当移动设备使用令牌向您的应用发出 API 请求时,它应将令牌作为 Bearer
令牌传递在 Authorization
头中。
为移动应用颁发令牌时,您也可以自由指定令牌能力
保护路由
如前所述,您可以通过将 sanctum
认证守卫附加到路由来保护路由,以便所有传入请求都必须经过认证。通常,您会将此守卫附加到 routes/api.php
文件中定义的路由:
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
return $request->user();
});
撤销令牌
为了允许用户撤销颁发给移动设备的 API 令牌,您可以在 Web 应用的“账户设置”部分列出它们的名称,并附带一个“撤销”按钮。当用户点击“撤销”按钮时,您可以从数据库中删除令牌。请记住,您可以通过 HasApiTokens
trait 提供的 tokens
关系访问用户的 API 令牌:
// 撤销所有令牌...
$user->tokens()->delete();
// 撤销特定令牌...
$user->tokens()->where('id', $id)->delete();
测试
在测试时,可以使用 Sanctum::actingAs
方法来认证用户并指定其令牌授予的能力:
use App\User;
use Laravel\Sanctum\Sanctum;
public function test_task_list_can_be_retrieved()
{
Sanctum::actingAs(
factory(User::class)->create(),
['view-tasks']
);
$response = $this->get('/api/task');
$response->assertOk();
}
如果您希望将所有能力授予令牌,您应该在提供给 actingAs
方法的能力列表中包含 *
:
Sanctum::actingAs(
factory(User::class)->create(),
['*']
);