Sanctum 轻量级 API 认证
Laravel Sanctum
介绍
Laravel Sanctum 为 SPA (单页面应用程序)、移动应用程序和简单的、基于令牌的 API 提供轻量级身份验证系统。Sanctum 允许应用程序的每个用户为他们的帐户生成多个 API 令牌。这些令牌可能被授予指定允许令牌执行哪些操作的能力/范围。
工作原理
API 令牌
Laravel Sanctum 是为了解决两个独立问题而生。 首先,它是一个简单的包,用于向用户发出 API 令牌,而不涉及 OAuth。这个功能的灵感来自 GitHub 的「访问令牌」。例如,假设应用程序的「帐户设置」有一个界面,用户可以在其中为其帐户生成 API 令牌。您可以使用 Sanctum 来生成和管理这些令牌。这些令牌通常有很长的过期时间(以年计),当然用户是可以随时手动撤销它们的。
Laravel Sanctum 的这个特性是通过将用户 API 令牌存储在单个数据库表中,并通过包含了有效 API 令牌的Authorization
标头对传入请求进行身份验证而实现的。
SPA 身份验证
「提示」Sanctum 适用于 API 令牌认证或 SPA 身份认证,使用 Sanctum 并不意味着你需要用到它所提供全部特性。
Sanctum 提供了一种简单的方法来认证需要与基于 Laravel 的 API 进行通信的单页应用程序 (SPAs)。这些 SPA 可能与 Laravel 应用程序存在于同一仓库中,也可能是一个完全独立的仓库,例如使用 Vue CLI 创建的单页应用程序
对于此功能,Sanctum 不使用任何类型的令牌。相反,Sanctum 使用 Laravel 内置的基于 cookie 的会话身份验证服务。这提供了CSRF保护,会话身份验证以及防止因 XSS 攻击而泄漏身份验证凭据。仅当传入请求来自您自己的 SPA 前端时,Sanctum 才会尝试使用 Cookie 进行身份验证。
安装过程
你可以通过 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,你需要在 app/Http/Kernel.php
文件中将 Sanctum 的中间件添加到你的 api
中间件组中:
use Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful;
'api' => [
EnsureFrontendRequestsAreStateful::class,
'throttle:60,1',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
API 令牌认证
提示:当需要为SPA应用选择认证方案的时候,应该首选 Sanctum 内置的 SPA认证 而不是API令牌。
发行 API 令牌
可以使用 Sanctum 发行 API令牌/个人访问令牌 对你的API请求进行认证。 当使用API令牌进行请求的时,令牌可以以Bearer
的形式包含在Authorization
header头里。
给用户发行令牌的时候,User 模型里应该使用 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可以为令牌分配 “abilities” ,类似于OAuth的 “scopes”。可以将字符串能力数组作为第二个参数传递给 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应用的有状态的已认证请求,也是一个包含了有效令牌头的已认证请求:
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
return $request->user();
});
撤销令牌
我们可以使用HasApiTokens
trait提供的 tokens
关联关系从数据库删除它们,以达到撤销令牌的目的:
// Revoke all tokens...
$user->tokens()->delete();
// Revoke a specific token...
$user->tokens()->where('id', $id)->delete();
SPA 认证
Sanctum为单页面应用 (SPAs) 与Laravel支持的API之间进行通信提供了一套简便的认证方法。这些SPAs 可以与你的Laravel 应用在同一个存储层中,也可以完全分离于存储层之外,比如通过 Vue CLI构建的SPA。
对于这个特性,Sanctum不使用其他任何类型的令牌。相反,Sanctum 使用的是Laravel内置的基于cookie的session认证服务。这带来了诸多好处,比如CSRF保护,以及防止通过XSS泄漏身份验证凭据。Sanctum 只会在传入的请求来自于你自己的SPA前端时尝试使用cookie进行身份验证。
配置
配置你的第一方域
首先,你应该配置你的单页面应用将从哪个域发出请求。你可以使用Sanctum
配置文件中的stateful
配置选项配置这些域。此配置设置确定在向你的API发出请求时,哪些域将使用Laravel会话cookie来维持“有状态的”身份验证。
Sanctum 中间件
接下来,你应该将Sanctum的中间件添加到app/Http/Kernel.php
文件中的api
中间件组中。该中间件负责确保来自单页面应用的传入请求可以使用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
配置文件中配置应用程序的CORS设置。
另外,你应该在全局axios
实例上启用withCredentials
选项。通常,这应该在你的resources/js/bootstrap.js
文件中执行:
axios.defaults.withCredentials = true;
最后,你应确保应用程序的会话cookie域配置支持根域的任何子域。您可以在您的session
配置文件中用前导.
作为域的前缀:
'domain' => '.domain.com',
验证
要对单页面应用进行身份验证,你的单页面应用的登录页面应首先向/Sanctum/csrf-cookie
路由发出请求,以初始化应用程序的CSRF保护:
axios.get('/sanctum/csrf-cookie').then(response => {
// 登录...
});
初始化CSRF保护后,你应该对典型的Laravel/login
路由发出POST
请求。此/login
路由可以由laravel/ui
authentication scaffolding软件包提供。
如果登录请求成功,则将对你进行身份验证,并且随后通过Laravel后端发布给你的客户端的会话cookie,自动验证对API路由的后续请求。
{提示}你可以自由编写自己的
/login
端点;但是,你应确保使用标准的Laravel提供的基于会话的身份验证服务对用户进行身份验证。
路由保护
为了保护路由,因此必须对所有传入的请求进行身份验证,你应该在routes/api.php
文件中为你的API路由附加Sanctum
授权看守器。如果请求来自你的单页面应用,此看守器会确保传入的请求被验证为有状态的已验证请求,如果请求来自第三方,它将使请求包含有效的API令牌头:
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
return $request->user();
});
授权私有广播频道
如果你的单页面应用需要通过private / presence broadcast channels进行身份认证,你需要在你的routes/api.php
文件中调用Broadcast::routes
方法:
Broadcast::routes(['middleware' => ['auth:sanctum']]);
接下来, 为了使Pusher的授权请求成功, 你需要在初始化Laravel Echo时提供自定义的Pusherauthorizer
。这可以让你的应用程序将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令牌
开始时,创建接受用户的电子邮件/用户名、密码和设备名称的路由,然后将这些凭据交换为新的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' => ['The provided credentials are incorrect.'],
]);
}
return $user->createToken($request->device_name)->plainTextToken;
});
当移动设备使用令牌向你的应用程序发出API请求时,它应将令牌作为Bearer
令牌传递到Authorization
请求头中。
{提示} 在为移动应用程序发行令牌时,您还可以自由指定 token abilities
路由保护
如前所述,您需要保护路由,因此必须通过在路由上附加Sanctum
身份验证看守器来对所有传入请求进行身份验证。一般来说,你会将此看守器附加到routes/api.php
文件中定义的路由上:
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
return $request->user();
});
撤销令牌
为了允许用户撤销发布给移动设备的API令牌,你可以在Web应用程序UI的“帐户设置”部分中按名称列出它们,并带有“撤销”按钮。当用户单击“撤消”按钮时,可以从数据库中删除令牌。请记住,您可以通过HasApiTokens
特性提供的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(),
['*']
);