Skip to content

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 进行认证。

lightbulb

仅使用 Sanctum 进行 API 令牌认证或仅使用 SPA 认证是完全可以的。使用 Sanctum 并不意味着您必须使用它提供的两种功能。

安装

您可以通过 Composer 安装 Laravel Sanctum:

php
composer require laravel/sanctum

接下来,您应该使用 vendor:publish Artisan 命令发布 Sanctum 配置和迁移文件。sanctum 配置文件将被放置在您的 config 目录中:

php
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"

最后,您应该运行数据库迁移。Sanctum 将创建一个数据库表来存储 API 令牌:

php
php artisan migrate

接下来,如果您计划使用 Sanctum 来认证 SPA,您应该将 Sanctum 的中间件添加到 app/Http/Kernel.php 文件中的 api 中间件组中:

php
use Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful;

'api' => [
    EnsureFrontendRequestsAreStateful::class,
    'throttle:60,1',
    \Illuminate\Routing\Middleware\SubstituteBindings::class,
],

迁移自定义

如果您不打算使用 Sanctum 的默认迁移,您应该在 AppServiceProviderregister 方法中调用 Sanctum::ignoreMigrations 方法。您可以使用 php artisan vendor:publish --tag=sanctum-migrations 导出默认迁移。

API 令牌认证

lightbulb

您不应使用 API 令牌来认证您自己的第一方 SPA。相反,请使用 Sanctum 的内置 SPA 认证

颁发 API 令牌

Sanctum 允许您颁发可用于认证 API 请求的 API 令牌/个人访问令牌。使用 API 令牌进行请求时,令牌应作为 Bearer 令牌包含在 Authorization 头中。

要开始为用户颁发令牌,您的用户模型应使用 HasApiTokens trait:

php
use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable
{
    use HasApiTokens, Notifiable;
}

要颁发令牌,您可以使用 createToken 方法。createToken 方法返回一个 Laravel\Sanctum\NewAccessToken 实例。API 令牌在存储到数据库之前使用 SHA-256 哈希进行哈希处理,但您可以使用 NewAccessToken 实例的 plainTextToken 属性访问令牌的纯文本值。您应在令牌创建后立即将此值显示给用户:

php
$token = $user->createToken('token-name');

return $token->plainTextToken;

您可以使用 HasApiTokens trait 提供的 tokens Eloquent 关系访问用户的所有令牌:

php
foreach ($user->tokens as $token) {
    //
}

令牌能力

Sanctum 允许您为令牌分配“能力”,类似于 OAuth 的“范围”。您可以将字符串能力数组作为第二个参数传递给 createToken 方法:

php
return $user->createToken('token-name', ['server:update'])->plainTextToken;

处理由 Sanctum 认证的传入请求时,您可以使用 tokenCan 方法确定令牌是否具有给定能力:

php
if ($user->tokenCan('server:update')) {
    //
}
lightbulb

为方便起见,如果传入的认证请求来自您的第一方 SPA 并且您正在使用 Sanctum 的内置 SPA 认证,则 tokenCan 方法将始终返回 true

保护路由

要保护路由以便所有传入请求都必须经过认证,您应该将 sanctum 认证守卫附加到 routes/api.php 文件中的 API 路由。此守卫将确保传入请求被认证为来自您的 SPA 的有状态认证请求,或者如果请求来自第三方,则包含有效的 API 令牌头:

php
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
    return $request->user();
});

撤销令牌

您可以通过使用 HasApiTokens trait 提供的 tokens 关系从数据库中删除令牌来“撤销”令牌:

php
// 撤销所有令牌...
$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 进行认证。

exclamation

为了进行认证,您的 SPA 和 API 必须共享相同的顶级域名。然而,它们可以放置在不同的子域上。

配置

配置您的第一方域

首先,您应该配置您的 SPA 将从哪些域发出请求。您可以使用 sanctum 配置文件中的 stateful 配置选项来配置这些域。此配置设置确定哪些域在向您的 API 发出请求时将使用 Laravel 会话 cookie 进行“有状态”认证。

exclamation

如果您通过包含端口的 URL(如 127.0.0.1:8000)访问您的应用,您应该确保在域中包含端口号。

Sanctum 中间件

接下来,您应该将 Sanctum 的中间件添加到 app/Http/Kernel.php 文件中的 api 中间件组中。此中间件负责确保来自您的 SPA 的传入请求可以使用 Laravel 的会话 cookie 进行认证,同时仍然允许来自第三方或移动应用的请求使用 API 令牌进行认证:

php
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 文件中执行:

php
axios.defaults.withCredentials = true;

最后,您应该确保应用的会话 cookie 域配置支持根域的任何子域。您可以通过在 session 配置文件中在域前加上一个前导 . 来实现这一点:

php
'domain' => '.domain.com',

认证

要认证您的 SPA,您的 SPA 的登录页面应首先向 /sanctum/csrf-cookie 路由发出请求,以初始化应用的 CSRF 保护:

php
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 自动进行认证。

lightbulb

您可以自由编写自己的 /login 端点;然而,您应该确保它使用 Laravel 提供的标准基于会话的认证服务来认证用户。

保护路由

要保护路由以便所有传入请求都必须经过认证,您应该将 sanctum 认证守卫附加到 routes/api.php 文件中的 API 路由。此守卫将确保传入请求被认证为来自您的 SPA 的有状态认证请求,或者如果请求来自第三方,则包含有效的 API 令牌头:

php
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
    return $request->user();
});

授权私有广播频道

如果您的 SPA 需要与私有/存在广播频道进行认证,您应该将 Broadcast::routes 方法调用放在 routes/api.php 文件中:

php
Broadcast::routes(['middleware' => ['auth:sanctum']]);

接下来,为了使 Pusher 的授权请求成功,您需要在初始化 Laravel Echo 时提供一个自定义的 Pusher authorizer。这允许您的应用配置 Pusher 以使用正确配置用于跨域请求的 axios 实例:

php
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 请求:

php
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 头中。

lightbulb

为移动应用颁发令牌时,您也可以自由指定令牌能力

保护路由

如前所述,您可以通过将 sanctum 认证守卫附加到路由来保护路由,以便所有传入请求都必须经过认证。通常,您会将此守卫附加到 routes/api.php 文件中定义的路由:

php
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
    return $request->user();
});

撤销令牌

为了允许用户撤销颁发给移动设备的 API 令牌,您可以在 Web 应用的“账户设置”部分列出它们的名称,并附带一个“撤销”按钮。当用户点击“撤销”按钮时,您可以从数据库中删除令牌。请记住,您可以通过 HasApiTokens trait 提供的 tokens 关系访问用户的 API 令牌:

php
// 撤销所有令牌...
$user->tokens()->delete();

// 撤销特定令牌...
$user->tokens()->where('id', $id)->delete();

测试

在测试时,可以使用 Sanctum::actingAs 方法来认证用户并指定其令牌授予的能力:

php
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 方法的能力列表中包含 *

php
Sanctum::actingAs(
    factory(User::class)->create(),
    ['*']
);