# Laravel Sanctum
- [介绍](#introduction)
- [工作原理](#how-it-works)
- [安装](#installation)
- [API 令牌认证](#api-token-authentication)
- [发行 API 令牌](#issuing-api-tokens)
- [令牌能力](#token-abilities)
- [保护路由](#protecting-routes)
- [撤销令牌](#revoking-tokens)
- [SPA 认证](#spa-authentication)
- [配置](#spa-configuration)
- [认证](#spa-authenticating)
- [保护路由](#protecting-spa-routes)
- [授权专用广播频道](#authorizing-private-broadcast-channels)
- [移动应用认证](#mobile-application-authentication)
- [发行 API 令牌](#issuing-mobile-api-tokens)
- [保护路由](#protecting-mobile-api-routes)
- [撤销令牌](#revoking-mobile-api-tokens)
- [测试](#testing)
## 介绍
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认证](#spa-authentication) 而不是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 认证](#spa-authentication),当一个已经认证的请求进来的时候,`tokenCan` 方法将总是返回 `true`
### 保护路由
为了保护路由,所有进来的请求都必须进行认证,应该将 `Sanctum` 认证守卫附加到 `routes/api.php` 的API路由里。如果一个请求是来自第三方的请求,这个守卫会确保进来的请求既是一个你的SPA应用的有状态的已认证请求,也是一个包含了有效令牌头的已认证请求:
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
return $request->user();
});
### 撤销令牌
我们可以使用`HasApiTokens` trait提供的 `tokens` 关联关系从数据库删除它们,以达到撤销令牌的目的:
```php
// 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](/docs/{{version}}/authentication#introduction)软件包提供。
如果登录请求成功,则将对你进行身份验证,并且随后通过Laravel后端发布给你的客户端的会话cookie,自动验证对API路由的后续请求。
> {提示}你可以自由编写自己的`/login`端点;但是,你应确保使用标准的[Laravel提供的基于会话的身份验证服务](/docs/{{version}}/authentication#authenticating-users)对用户进行身份验证。
### 路由保护
为了保护路由,因此必须对所有传入的请求进行身份验证,你应该在`routes/api.php`文件中为你的API路由附加`Sanctum` 授权看守器。如果请求来自你的单页面应用,此看守器会确保传入的请求被验证为有状态的已验证请求,如果请求来自第三方,它将使请求包含有效的API令牌头:
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
return $request->user();
});
### 授权私有广播频道
如果你的单页面应用需要通过[private / presence broadcast channels](/docs/{{version}}/broadcasting#authorizing-channels)进行身份认证,你需要在你的`routes/api.php`文件中调用`Broadcast::routes`方法:
Broadcast::routes(['middleware' => ['auth:sanctum']]);
接下来, 为了使Pusher的授权请求成功, 你需要在初始化[Laravel Echo](/docs/{{version}}/broadcasting#installing-laravel-echo)时提供自定义的Pusher`authorizer`。这可以让你的应用程序将Pusher配置为 [为了跨域请求正确配置的](#cors-and-cookies)`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](#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(),
['*']
);