Sanctum 认证

未匹配的标注

Laravel 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)。这些 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(),
    ['*']
);

本文章首发在 LearnKu.com 网站上。

本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
上一篇 下一篇
Summer
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
贡献者:5
讨论数量: 0
发起讨论 只看当前版本


暂无话题~