跳到主要内容
版本:10.x

Laravel Passport

简介

Laravel Passport 可以在几分钟之内为你的应用程序提供完整的 OAuth2 服务端实现。Passport 是基于由 Andy Millington 和 Simon Hamp 维护的 League OAuth2 server 建立的。

注意

本文档假定你已熟悉 OAuth2 。如果你并不了解 OAuth2 ,阅读之前请先熟悉下 OAuth2 的 常用术语 和特性。

Passport 还是 Sanctum?

在开始之前,我们希望你先确认下是 Laravel Passport 还是 Laravel Sanctum 能为你的应用提供更好的服务。如果你的应用确确实实需要支持 OAuth2,那没疑问,你需要选用 Laravel Passport。

然而,如果你只是试图要去认证一个单页应用,或者手机应用,或者发布 API 令牌,你应该选用 Laravel Sanctum。 Laravel Sanctum 不支持 OAuth2,它提供了更为简单的 API 授权开发体验。

安装

在开始使用之前,使用 Composer 包管理器安装 Passport:

composer require laravel/passport

Passport 的 服务提供器 注册了自己的数据库迁移脚本目录, 所以你应该在安装软件包完成后迁移你自己的数据库。 Passport 的迁移脚本将为你的应用创建用于存储 OAuth2 客户端和访问令牌的数据表:

php artisan migrate

接下来,你需要执行 Artisan 命令 passport:install。这个命令将会创建一个用于生成安全访问令牌的加密秘钥。另外,这个命令也将创建用于生成访问令牌的 「个人访问」 客户端和 「密码授权」 客户端 :

php artisan passport:install
提示

如果你想用使用 UUID 作为 Passport Client 模型的主键,代替默认的自动增长整形字段,请在安装 Passport 时使用 uuids 参数

在执行 passport:install 命令后, 添加 Laravel\Passport\HasApiTokens trait 到你的 App\Models\User 模型中。 这个 trait 会提供一些帮助方法用于检查已认证用户的令牌和权限范围。如果你的模型已经在使用 Laravel\Sanctum\HasApiTokens trait,你可以删除该 trait:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Passport\HasApiTokens;

class User extends Authenticatable
{
use HasApiTokens, HasFactory, Notifiable;
}

最后,在您的应用的 config/auth.php 配置文件中,您应当定义一个 api 的授权看守器,并且将其 driver 选项设置为 passport 。这个调整将会让您的应用程序使用 Passport 的 TokenGuard 来鉴权 API 接口请求:

'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],

'api' => [
'driver' => 'passport',
'provider' => 'users',
],
],

客户端 UUID

您也可以在运行 passport:install 命令的时候使用 --uuids 选项。这个参数将会让 Passport 使用 UUID 来替代默认的自增长形式的 Passport Client 模型主键。在您运行带有 --uuids 参数的 passport:install 命令后,您将得到关于禁用 Passport 默认迁移的相关指令说明:

php artisan passport:install --uuids

部署 Passport

在您第一次部署 Passport 到您的应用服务器时,您需要执行 passport:keys 命令。该命令用于生成 Passport 用于生成 access token 的一个加密密钥。生成的加密密钥不应到添加到源代码控制系统中:

php artisan passport:keys

如有必要,您可以定义 Passport 的密钥应当加载的位置。您可以使用 Passport:loadKeysFrom 方法来实现。通常,这个方法应当在您的 App\Providers\AuthServiceProvider 类的 boot 方法中调用:

/**
* Register any authentication / authorization services.
*/
public function boot(): void
{
Passport::loadKeysFrom(__DIR__.'/../secrets/oauth');
}

从环境中加载密钥

此外,您可以使用 vendor:publish Artisan 命令来发布您的 Passport 配置文件:

php artisan vendor:publish --tag=passport-config

在发布配置文件之后,您可以将加密密钥配置为环境变量,再加载它们:

PASSPORT_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----
<private key here>
-----END RSA PRIVATE KEY-----"

PASSPORT_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----
<public key here>
-----END PUBLIC KEY-----"

自定义迁移

如果您不打算使用 Passport 的默认迁移,您应当在 App\Providers\AppServiceProvider 类的 register 方法中调用 Passport::ignoreMigrations 方法。您可以 使用 vendor:publish Artisan 命令来导出默认的迁移文件:

php artisan vendor:publish --tag=passport-migrations

Passport 的升级

当升级到 Passport 的主要版本时,请务必查阅 升级指南.

配置

客户端密钥的 Hash 加密

如果您希望客户端密钥在存储到数据库时使用 Hash 对其进行加密,您应当在 App\Provider\AuthServiceProvider 类的 boot 方法中调用 Passport:hashClientSecrets

use Laravel\Passport\Passport;

Passport::hashClientSecrets();

一旦启用后,所有的客户端密钥都将只在创建的时候显示。由于明文的客户端密钥没有存储到数据库中,因此一旦其丢失后便无法恢复。

Token 生命周期

默认情况下,Passport 会颁发长达一年的长期 token 。如果您想要配置一个更长或更短的 token 生命周期,您可以在 App\Provider\AuthServiceProvider 类的 boot 方法中调用 tokensExpiresInrefresgTokensExpireInpersonalAccessTokensExpireIn 方法:

/**
* 注册身份验证/授权服务。
*/
public function boot(): void
{
Passport::tokensExpireIn(now()->addDays(15));
Passport::refreshTokensExpireIn(now()->addDays(30));
Passport::personalAccessTokensExpireIn(now()->addMonths(6));
}
注意

Passport 数据库表中的 expires_at 列是只读的,仅仅用于显示。在颁发 token 的时候,Passport 将过期信息存储在已签名和加密的 token 中。如果你想让 token 失效,你应当 撤销它

重写 Passport 的默认模型

您可以通过定义自己的模型并继承相应的 Passport 模型来实现自由自由扩展 Passport 内部使用的模型:

use Laravel\Passport\Client as PassportClient;

class Client extends PassportClient
{
// ...
}

在定义您的模型之后,您可以在 Laravel\Passport\Passport 类中指定 Passport 使用您自定义的模型。一样的,您应该在应用程序的 App\Providers\AuthServiceProvider 类中的 boot 方法中指定 Passport 使用您自定义的模型:

use App\Models\Passport\AuthCode;
use App\Models\Passport\Client;
use App\Models\Passport\PersonalAccessClient;
use App\Models\Passport\RefreshToken;
use App\Models\Passport\Token;

/**
* 注册任意认证/授权服务。
*/
public function boot(): void
{
Passport::useTokenModel(Token::class);
Passport::useRefreshTokenModel(RefreshToken::class);
Passport::useAuthCodeModel(AuthCode::class);
Passport::useClientModel(Client::class);
Passport::usePersonalAccessClientModel(PersonalAccessClient::class);
}

重写路由

您可能希望自定义 Passport 定义的路由。要实现这个功能,第一步,您需要在应用程序的 AppServiceProvider 中的 register 方法中添加 Passport:ignoreRoutes 语句,以忽略由 Passport 注册的路由:

use Laravel\Passport\Passport;

/**
* 注册任意的应用程序服务。
*/
public function register(): void
{
Passport::ignoreRoutes();
}

然后,您可以复制 Passport 在自己的文件中 定义的路由到应用程序的 routes/web.php 文件中,并且将其修改为您喜欢的任何形式:

Route::group([
'as' => 'passport.',
'prefix' => config('passport.path', 'oauth'),
'namespace' => 'Laravel\Passport\Http\Controllers',
], function () {
// Passport 路由……
});

发布访问令牌

通过授权码使用 OAuth2 是大多数开发人员熟悉的方式。使用授权码方式时,客户端应用程序会将用户重定向到你的服务器,在那里他们会批准或拒绝向客户端发出访问令牌的请求。

客户端管理

首先,开发者如果想要搭建一个与你的服务端接口交互的应用端,需要在服务端这边注册一个「客户端」。通常,这需要开发者提供应用程序的名称和一个 URL,在应用软件的使用者授权请求后,应用程序会被重定向到该 URL。

passport:client 命令

使用 Artisan 命令 passport:client 是一种最简单的创建客户端的方式。 这个命令可以创建你自己私有的客户端,用于 Oauth2 功能测试。 当你执行 client 命令后, Passport 将会给你更多关于客户端的提示,以及生成的客户端 ID

php artisan passport:client

多重定向 URL 地址的设置

如果你想为你的客户端提供多个重定向 URL ,你可以在执行 Passport:client 命令出现提示输入 URL 地址的时候,输入用逗号分割的多个 URL 。任何包含逗号的 URL 都需要先执行 URL 转码:

http://example.com/callback,http://examplefoo.com/callback

JSON API

因为应用程序的开发者是无法使用 client 命令的,所以 Passport 提供了 JSON 格式的 API ,用于创建客户端。 这解决了你还要去手动创建控制器代码(代码用于添加,更新,删除客户端)的麻烦。

但是,你需要结合 Passport 的 JSON API 接口和你的前端面板管理页面, 为你的用户提供客户端管理功能。接下里,我们会回顾所有用于管理客户端的的 API 接口。方便起见,我们使用 Axios 模拟对端点的 HTTP 请求。

这些 JSON API 接口被 web 和 auth 两个中间件保护着,因此,你只能从你的应用中调用。 外部来源的调用是被禁止的。

GET /oauth/clients

下面的路由将为授权用户返回所有的客户端。最主要的作用是列出所有的用户客户端,接下来就可以编辑或删除它们了:

axios.get('/oauth/clients')
.then(response => {
console.log(response.data);
});

POST /oauth/clients

下面的路由用于创建新的客户端。 它需要两个参数: 客户端名称重定向URL 地址。 重定向URL 地址是使用者在授权或者拒绝授权后被重定向到的地方。

客户端被创建后,将会生成客户端 ID 和客户端秘钥。 这对值用于从你的应用获取访问令牌。 调用下面的客户端创建路由将创建新的客户端实例:

const data = {
name: 'Client Name',
redirect: 'http://example.com/callback'
};

axios.post('/oauth/clients', data)
.then(response => {
console.log(response.data);
})
.catch (response => {
// 列出响应的错误...
});

PUT /oauth/clients/ \{#put-oauthclientsclient-id}{client-id}

下面的路由用来更新客户端。它需要两个参数: 客户端名称和重定向 URL 地址。 重定向 URL 地址是用户在授权或者拒绝授权后被重定向到的地方。路由将返回更新后的客户端实例:

const data = {
name: 'New Client Name',
redirect: 'http://example.com/callback'
};

axios.put('/oauth/clients/' + clientId, data)
.then(response => {
console.log(response.data);
})
.catch (response => {
// 列出响应的错误...
});

DELETE /oauth/clients/ \{#delete-oauthclientsclient-id}{client-id}

下面的路由用于删除客户端:

axios.delete('/oauth/clients/' + clientId)
.then(response => {
// ...
});

请求令牌

授权重定向

客户端创建好后,开发者使用 client ID 和秘钥向你的应用服务器发送请求,以便获取授权码和访问令牌。 首先,接收到请求的业务端服务器会重定向到你应用的 /oauth/authorize 路由上,如下所示:

use Illuminate\Http\Request;
use Illuminate\Support\Str;

Route::get('/redirect', function (Request $request) {
$request->session()->put('state', $state = Str::random(40));

$query = http_build_query([
'client_id' => 'client-id',
'redirect_uri' => 'http://third-party-app.com/callback',
'response_type' => 'code',
'scope' => '',
'state' => $state,
// 'prompt' => '', // "none", "consent", or "login"
]);

return redirect('http://passport-app.test/oauth/authorize?'.$query);
});

prompt 参数可用于指定 Passport 应用程序的认证行为。

如果 prompt 值为 none,如果用户还没有通过 Passport 应用程序的认证,Passport 将总是抛出一个认证错误。如果值是 同意,Passport 将总是显示授权批准屏幕,即使所有的作用域以前都被授予消费应用程序。如果值是 login,Passport 应用程序将总是提示用户重新登录到应用程序,即使他们已经有一个现有的会话。

如果没有提供 prompt 值,只有当用户以前没有授权访问所请求范围的消费应用程序时,才会提示用户进行授权。

提示

请记住,/oauth/authorize 路由默认已经在 Passport::route 方法中定义,你无需手动定义它。

请求认证

当接收到一个请求后, Passport 会自动展示一个模板页面给用户,用户可以选择授权或者拒绝授权。如果请求被认证,用户将被重定向到之前业务服务器设置的 redirect_uri 上去。 这个 redirect_uri 就是客户端在创建时提供的重定向地址参数。

如果你想自定义授权页面,你可以先使用 Artisan 命令 vendor:publish 发布 Passport 的视图页面。 被发布的视图页面位于 resources/views/vendor/passport 路径下:

php artisan vendor:publish --tag=passport-views

有时,你可能希望跳过授权提示,比如在授权第一梯队客户端的时候。你可以通过 继承 Client 模型并实现 skipsAuthorization 方法。如果 skipsAuthorization 方法返回 true, 客户端就会直接被认证并立即重定向到设置的重定向地址:

<?php

namespace App\Models\Passport;

use Laravel\Passport\Client as BaseClient;

class Client extends BaseClient
{
/**
* 确定客户端是否应跳过授权提示。
*/
public function skipsAuthorization(): bool
{
return $this->firstParty();
}
}

授权码到授权令牌的转化

如果用户授权了访问,他们会被重定向到业务服务端。首先,业务端服务需要检查 state 参数是否和重定向之前存储的值一致。 如果 state 参数的值正确,业务端服务器需要对你的应用发