介绍 Laravel Sanctum 为 SPA (单页面应用程序)、移动应用程序和简单的、基于令牌的 API 提供轻量级身份验证系统。Sanctum 允许应用程序的每个用户为他们的帐户生成多个 API 令牌。这些令牌可能被授予指定允许令牌执行哪些操作的能力 / 范围。 工作原理 Laravel Sanctum 是为了解决两个独立问题而生。 API 令牌 首先,它是一个简单的包,用于向用户发出 API 令牌,而不涉及 OAuth。这个功能的灵感来自 GitHub 的「访问令牌」。例如,假设应用程序的「帐户设置」有一个界面,用户可以在其中为其帐户生成 API 令牌。您可以使用 Sanctum 来生成和管理这些令牌。这些令牌通常有很长的过期时间 (以年计),当然用户是可以随时手动撤销它们的。 Laravel Sanctum 的这个特性是通过将用户 API 令牌存储在单个数据库表中,并通过包含了有效 API 令牌的 Authorization 标头对传入请求进行身份验证而实现的。 SPA 身份验证 Sanctum 提供了一种简单的方法来认证需要与基于 Laravel 的 API 进行通信的单页应用程序 (SPAs)。这些 SPA 可能与 Laravel 应用程序存在于同一仓库中,也可能是一个完全独立的仓库,例如使用 Vue CLI 创建的单页应用程序。 对于此功能,Sanctum 不使用任何类型的令牌。相反,Sanctum 使用 Laravel 内置的基于 cookie 的会话身份验证服务。这提供了 CSRF 保护,会话身份验证以及防止因 XSS 攻击而泄漏身份验证凭据。仅当传入请求来自您自己的 SPA 前端时,Sanctum 才会尝试使用 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,你需要在 app/Http/Kernel.php 文件中将 Sanctum 的中间件添加到你的 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 令牌认证 技巧:当需要为 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 关联关系从数据库删除它们,以达到撤销令牌的目的: ``` // 撤销所有令牌... $user->tokens()->delete(); // 撤销用户最近的令牌... $request->user()->currentAccessToken()->delete(); // 撤销一个特定的令牌... $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 进行身份验证。 注意:为了完成认证,你的 SPA 与 API 必须共享同一个顶级域名。 但是,它们可以被放置在不同的次级域名上。 配置 配置你的第一方域 首先,你应该配置你的单页面应用将从哪个域发出请求。你可以使用 Sanctum 配置文件中的 stateful 配置选项配置这些域。此配置设置确定在向你的 API 发出请求时,哪些域将使用 Laravel 会话 cookie 来维持 “有状态的” 身份验证。 注意:如果你可以通过一个带有端口号的 URL (127.0.0.1:8000) 进入你的应用,你必须保证你的域名里包含这个端口号。 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 => { // 登录... }); ``` 在此请求期间,Laravel 将设置一个包含当前 CSRF 令牌的 XSRF-TOKENcookie。然后,该令牌应该在后续请求的 X-XSRF-token 头中传递,这是像 Axios 和 Angular HttpClient 这样的库自动为您完成的。 初始化 CSRF 保护后,你应该对典型的 Laravel/login 路由发出 POST 请求。此 /login 路由可以由 laravel/jetstreamauthentication scaffolding 软件包提供。 如果登录请求成功,则将对你进行身份验证,并且随后通过 Laravel 后端发布给你的客户端的会话 cookie,自动验证对 API 路由的后续请求。 技巧:你可以自由编写自己的 /login 端点;但是,你应确保使用标准的 session based authentication services that Laravel provides 对用户进行身份验证。 路由保护 为了保护路由,因此必须对所有传入的请求进行身份验证,你应该在 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 时提供自定义的 Pusher authorizer。这可以让你的应用程序将 Pusher 配置为 为了跨域请求正确配置的 axios 实例 properly configured for cross-domain requests: ``` 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\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 身份验证看守器来对所有传入请求进行身份验证。一般来说,你会将此看守器附加到 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\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(), ['*'] ); ```