Laravel 护照

Introduction

Laravel 护照 在几分钟内为您的 Laravel 应用程序提供完整的 OAuth2 服务器实现。护照建立在联盟 OAuth2 服务器 由 Andy Millington 和 Simon Hamp 维护。

Warning
本文档假定您已经熟悉 OAuth2。如果您对 OAuth2 一无所知,请考虑熟悉一般的terminology 和 OAuth2 的功能,然后再继续。

护照还是圣所?

在开始之前,您可能希望确定您的应用程序是否可以通过 Laravel Passport 或Laravel 圣殿.如果你的应用程序绝对需要支持 OAuth2,那么你应该使用 Laravel Passport。

但是,如果您尝试验证单页应用程序、移动应用程序或颁发 API 令牌,您应该使用Laravel 圣殿. Laravel Sanctum 不支持 OAuth2;但是,它提供了更简单的 API 身份验证开发体验。

Installation

首先,通过 Composer 包管理器安装 Passport:

composer require laravel/passport

护照的服务提供者 注册自己的数据库迁移目录,所以你应该在安装包后迁移你的数据库。 Passport 迁移将创建您的应用程序存储 OAuth2 客户端和访问令牌所需的表:

php artisan migrate

接下来,您应该执行passport:install 工匠命令。此命令将创建生成安全访问令牌所需的加密密钥。此外,该命令将创建“个人访问”和“密码授予”客户端,用于生成访问令牌:

php artisan passport:install

Note
如果您想使用 UUID 作为 Passport 的主键值Client 模型而不是自动递增整数,请使用安装 Passportuuids 选项.

运行后passport:install 命令,添加Laravel\Passport\HasApiTokens 你的特质App\Models\User 模型。此特征将为您的模型提供一些辅助方法,使您可以检查经过身份验证的用户的令牌和范围。如果您的模型已经在使用Laravel\Sanctum\HasApiTokens 特质,你可以删除那个特质:

<?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 而不是自动递增整数作为 PassportClient 模型的主键值。运行后passport:install 命令与--uuids 选项,您将获得有关禁用 Passport 的默认迁移的附加说明:

php artisan passport:install --uuids

部署护照

第一次将 Passport 部署到应用程序的服务器时,您可能需要运行passport:keys 命令。此命令生成 Passport 生成访问令牌所需的加密密钥。生成的密钥通常不保存在源代码管理中:

php artisan passport:keys

如有必要,您可以定义从中加载 Passport 密钥的路径。您可以使用Passport::loadKeysFrom 方法来实现这一点。通常,应从boot 你的应用程序的方法App\Providers\AuthServiceProvider 班级:

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

从环境中加载密钥

或者,您可以使用以下方式发布 Passport 的配置文件vendor:publish 工匠命令:

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 的默认迁移,你应该调用Passport::ignoreMigrations 中的方法register 你的方法App\Providers\AppServiceProvider 班级。您可以使用导出默认迁移vendor:publish 工匠命令:

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

升级护照

升级到 Passport 的新主要版本时,请务必仔细查看升级指南.

Configuration

客户端秘密散列

如果你希望你的客户的秘密在存储在你的数据库中时被散列,你应该调用Passport::hashClientSecrets 中的方法boot 你的方法App\Providers\AuthServiceProvider 班级:

use Laravel\Passport\Passport;

Passport::hashClientSecrets();

启用后,您的所有客户端机密都只能在创建后立即显示给用户。由于纯文本客户端密钥值从不存储在数据库中,因此如果丢失,则无法恢复密钥值。

令牌生命周期

默认情况下,Passport 颁发一年后过期的长期访问令牌。如果您想配置更长/更短的令牌生命周期,您可以使用tokensExpireIn,refreshTokensExpireIn, 和personalAccessTokensExpireIn 方法。这些方法应该从boot 你的应用程序的方法App\Providers\AuthServiceProvider 班级:

/**
 * Register any authentication / authorization services.
 */
public function boot(): void
{
    Passport::tokensExpireIn(now()->addDays(15));
    Passport::refreshTokensExpireIn(now()->addDays(30));
    Passport::personalAccessTokensExpireIn(now()->addMonths(6));
}

Warning
expires_at Passport 数据库表中的列是只读的,仅用于显示目的。颁发令牌时,Passport 将过期信息存储在已签名和加密的令牌中。如果您需要使令牌无效,您应该撤销它.

覆盖默认模型

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

use Laravel\Passport\Client as PassportClient;

class Client extends PassportClient
{
    // ...
}

定义模型后,您可以指示 Passport 通过Laravel\Passport\Passport 班级。通常,您应该在boot 你的应用程序的方法App\Providers\AuthServiceProvider 班级:

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;

/**
 * Register any authentication / authorization services.
 */
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 定义的路由。为此,您首先需要通过添加忽略 Passport 注册的路由Passport::ignoreRoutesregister 你的应用程序的方法AppServiceProvider:

use Laravel\Passport\Passport;

/**
 * Register any application services.
 */
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 routes...
});

发行访问令牌

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

管理客户

首先,构建需要与应用程序 API 交互的应用程序的开发人员需要通过创建“客户端”向您的应用程序注册他们的应用程序。通常,这包括提供他们的应用程序的名称和您的应用程序可以在用户批准他们的授权请求后重定向到的 URL。

passport:client 命令

创建客户端的最简单方法是使用passport:client 工匠命令。此命令可用于创建您自己的客户端以测试您的 OAuth2 功能。当你运行client命令,Passport 将提示您提供有关客户端的更多信息,并为您提供客户端 ID 和密码:

php artisan passport:client

重定向网址

如果您想为您的客户端允许多个重定向 URL,您可以在提示输入 URL 时使用逗号分隔列表指定它们passport:client 命令。任何包含逗号的 URL 都应该进行 URL 编码:

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

JSON API

由于您的应用程序的用户将无法使用client 命令,Passport 提供了一个 JSON API,您可以使用它来创建客户端。这为您省去了手动编写控制器代码以创建、更新和删除客户端的麻烦。

但是,您需要将 Passport 的 JSON API 与您自己的前端配对,以便为您的用户提供一个仪表板来管理他们的客户端。下面,我们将回顾所有用于管理客户端的 API 端点。为了方便,我们将使用Axios 演示向端点发出 HTTP 请求。

JSON API 由webauth 中间件;因此,它只能从您自己的应用程序中调用。无法从外部源调用它。

GET /oauth/clients

此路由返回经过身份验证的用户的所有客户端。这主要用于列出用户的所有客户端,以便他们可以编辑或删除它们:

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

POST /oauth/clients

此路由用于创建新客户端。它需要两条数据:客户的name 和一个redirect 网址。这redirect 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 => {
        // List errors on response...
    });

PUT /oauth/clients/{client-id}

此路由用于更新客户端。它需要两条数据:客户的name 和一个redirect 网址。这redirect 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 => {
        // List errors on response...
    });

DELETE /oauth/clients/{client-id}

此路由用于删除客户端:

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

请求令牌

重定向授权

创建客户端后,开发人员可以使用他们的客户端 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 将始终抛出身份验证错误。如果值为consent, Passport 将始终显示授权批准屏幕,即使之前已将所有范围授予消费应用程序。当值为login,Passport 应用程序将始终提示用户重新登录应用程序,即使他们已经有一个现有会话。

如果不prompt 如果提供了值,则仅当用户之前未授权访问所请求范围的消费应用程序时,才会提示用户进行授权。

Note
请记住,/oauth/authorize 路线已由 Passport 定义。您不需要手动定义此路由。

批准请求

当收到授权请求时,Passport会根据prompt 参数(如果存在),并且可以向用户显示一个模板,允许他们批准或拒绝授权请求。如果他们批准请求,他们将被重定向回redirect_uri 这是由消费应用程序指定的。这redirect_uri 必须匹配redirect 创建客户端时指定的 URL。

如果您想自定义授权批准屏幕,您可以使用发布 Passport 的视图vendor:publish 工匠命令。发布的视图将放置在resources/views/vendor/passport 目录:

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

有时您可能希望跳过授权提示,例如在授权第一方客户端时。你可以通过延长Client 模型 并定义一个skipsAuthorization 方法。如果skipsAuthorization 回报true 客户端将被批准,用户将被重定向回redirect_uri立即,除非消费应用程序已明确设置prompt 重定向授权时的参数:

<?php

namespace App\Models\Passport;

use Laravel\Passport\Client as BaseClient;

class Client extends BaseClient
{
    /**
     * Determine if the client should skip the authorization prompt.
     */
    public function skipsAuthorization(): bool
    {
        return $this->firstParty();
    }
}

将授权代码转换为访问令牌

如果用户批准授权请求,他们将被重定向回消费应用程序。消费者应首先验证state 参数与重定向之前存储的值相对应。如果状态参数匹配,那么消费者应该发出一个POST 请求您的应用程序请求访问令牌。该请求应包括在用户批准授权请求时由您的应用程序发出的授权代码:

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;

Route::get('/callback', function (Request $request) {
    $state = $request->session()->pull('state');

    throw_unless(
        strlen($state) > 0 && $state === $request->state,
        InvalidArgumentException::class
    );

    $response = Http::asForm()->post('http://passport-app.test/oauth/token', [
        'grant_type' => 'authorization_code',
        'client_id' => 'client-id',
        'client_secret' => 'client-secret',
        'redirect_uri' => 'http://third-party-app.com/callback',
        'code' => $request->code,
    ]);

    return $response->json();
});

/oauth/token 路由将返回包含的 JSON 响应access_token,refresh_token, 和expires_in 属性。这expires_in 属性包含访问令牌过期之前的秒数。

Note
/oauth/authorize 路线,/oauth/token 路线由 Passport 为您定义。无需手动定义此路由。

JSON API

Passport 还包括用于管理授权访问令牌的 JSON API。您可以将其与您自己的前端配对,为您的用户提供一个用于管理访问令牌的仪表板。为了方便,我们将使用Axios 演示向端点发出 HTTP 请求。 JSON API 由webauth 中间件;因此,它只能从您自己的应用程序中调用。

GET /oauth/tokens

此路由返回经过身份验证的用户创建的所有授权访问令牌。这主要用于列出所有用户的令牌,以便他们可以撤销它们:

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

DELETE /oauth/tokens/{token-id}

此路由可用于撤销授权访问令牌及其相关刷新令牌:

axios.delete('/oauth/tokens/' + tokenId);

刷新令牌

如果您的应用程序发布短期访问令牌,用户将需要通过在发布访问令牌时提供给他们的刷新令牌来刷新他们的访问令牌:

use Illuminate\Support\Facades\Http;

$response = Http::asForm()->post('http://passport-app.test/oauth/token', [
    'grant_type' => 'refresh_token',
    'refresh_token' => 'the-refresh-token',
    'client_id' => 'client-id',
    'client_secret' => 'client-secret',
    'scope' => '',
]);

return $response->json();

/oauth/token 路由将返回包含的 JSON 响应access_token,refresh_token, 和expires_in 属性。这expires_in 属性包含访问令牌过期之前的秒数。

撤销令牌

您可以使用revokeAccessToken 上的方法Laravel\Passport\TokenRepository.您可以使用revokeRefreshTokensByAccessTokenId 上的方法Laravel\Passport\RefreshTokenRepository.这些类可以使用 Laravel 的服务容器:

use Laravel\Passport\TokenRepository;
use Laravel\Passport\RefreshTokenRepository;

$tokenRepository = app(TokenRepository::class);
$refreshTokenRepository = app(RefreshTokenRepository::class);

// Revoke an access token...
$tokenRepository->revokeAccessToken($tokenId);

// Revoke all of the token's refresh tokens...
$refreshTokenRepository->revokeRefreshTokensByAccessTokenId($tokenId);

清除代币

当令牌被撤销或过期时,您可能希望将它们从数据库中清除。包括护照passport:purge Artisan 命令可以为您完成此操作:

# Purge revoked and expired tokens and auth codes...
php artisan passport:purge

# Only purge tokens expired for more than 6 hours...
php artisan passport:purge --hours=6

# Only purge revoked tokens and auth codes...
php artisan passport:purge --revoked

# Only purge expired tokens and auth codes...
php artisan passport:purge --expired

您还可以配置一个预定工作 在你的应用程序中App\Console\Kernel 类以按计划自动修剪您的令牌:

/**
 * Define the application's command schedule.
 */
protected function schedule(Schedule $schedule): void
{
    $schedule->command('passport:purge')->hourly();
}

使用 PKCE 授予授权码

带有“代码交换证明密钥”(PKCE) 的授权代码授予是一种安全的方式来验证单页应用程序或本机应用程序以访问您的 API。当您不能保证客户端机密将被机密存储或为了减轻攻击者拦截授权代码的威胁时,应使用此授权。在将授权代码交换为访问令牌时,“代码验证器”和“代码质询”的组合会替换客户端机密。

创建客户端

在您的应用程序可以通过 PKCE 授予的授权代码颁发令牌之前,您需要创建一个支持 PKCE 的客户端。您可以使用passport:client 工匠命令与--public 选项:

php artisan passport:client --public

请求令牌

代码验证器和代码挑战

由于此授权授予不提供客户端机密,因此开发人员将需要生成代码验证程序和代码质询的组合才能请求令牌。

代码验证器应该是 43 到 128 个字符的随机字符串,包含字母、数字和"-",".","_","~" 字符,定义在RFC 7636 规范.

代码质询应该是带有 URL 和文件名安全字符的 Base64 编码字符串。尾随'=' 应删除字符,并且不应出现换行符、空格或其他附加字符。

$encoded = base64_encode(hash('sha256', $code_verifier, true));

$codeChallenge = strtr(rtrim($encoded, '='), '+/', '-_');

重定向授权

创建客户端后,您可以使用客户端 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));

    $request->session()->put(
        'code_verifier', $code_verifier = Str::random(128)
    );

    $codeChallenge = strtr(rtrim(
        base64_encode(hash('sha256', $code_verifier, true))
    , '='), '+/', '-_');

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

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

将授权代码转换为访问令牌

如果用户批准授权请求,他们将被重定向回消费应用程序。消费者应核实state 参数与重定向之前存储的值相对应,如在标准授权代码授予中一样。

如果状态参数匹配,消费者应该发出一个POST 请求您的应用程序请求访问令牌。该请求应包括在用户批准授权请求时由您的应用程序发出的授权代码以及最初生成的代码验证程序:

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;

Route::get('/callback', function (Request $request) {
    $state = $request->session()->pull('state');

    $codeVerifier = $request->session()->pull('code_verifier');

    throw_unless(
        strlen($state) > 0 && $state === $request->state,
        InvalidArgumentException::class
    );

    $response = Http::asForm()->post('http://passport-app.test/oauth/token', [
        'grant_type' => 'authorization_code',
        'client_id' => 'client-id',
        'redirect_uri' => 'http://third-party-app.com/callback',
        'code_verifier' => $codeVerifier,
        'code' => $request->code,
    ]);

    return $response->json();
});

密码授予令牌

Warning
我们不再建议使用密码授予令牌。相反,你应该选择OAuth2 服务器当前推荐的授权类型.

OAuth2 密码授予允许您的其他第一方客户端(例如移动应用程序)使用电子邮件地址/用户名和密码获取访问令牌。这使您可以安全地向第一方客户端颁发访问令牌,而无需您的用户完成整个 OAuth2 授权代码重定向流程。

创建密码授予客户端

在您的应用程序可以通过密码授予颁发令牌之前,您需要创建一个密码授予客户端。您可以使用passport:client 工匠命令与--password 选项。如果您已经运行passport:install 命令,您不需要运行此命令:

php artisan passport:client --password

请求令牌

创建密码授予客户端后,您可以通过发出POST 要求/oauth/token 使用用户的电子邮件地址和密码进行路由。请记住,此路由已由 Passport 注册,因此无需手动定义。如果请求成功,您将收到一个access_tokenrefresh_token 在来自服务器的 JSON 响应中:

use Illuminate\Support\Facades\Http;

$response = Http::asForm()->post('http://passport-app.test/oauth/token', [
    'grant_type' => 'password',
    'client_id' => 'client-id',
    'client_secret' => 'client-secret',
    'username' => 'taylor@laravel.com',
    'password' => 'my-password',
    'scope' => '',
]);

return $response->json();

Note
请记住,默认情况下,访问令牌是长期有效的。但是,您可以自由配置您的最大访问令牌生命周期 如果需要的话。

请求所有范围

使用密码授予或客户端凭据授予时,您可能希望为您的应用程序支持的所有范围授权令牌。您可以通过请求* 范围。如果您要求* 范围,can 令牌实例上的方法将始终返回true.此范围只能分配给使用password 或者client_credentials 授予:

use Illuminate\Support\Facades\Http;

$response = Http::asForm()->post('http://passport-app.test/oauth/token', [
    'grant_type' => 'password',
    'client_id' => 'client-id',
    'client_secret' => 'client-secret',
    'username' => 'taylor@laravel.com',
    'password' => 'my-password',
    'scope' => '*',
]);

自定义用户提供者

如果您的应用程序使用多个身份验证用户提供者,您可以通过提供一个来指定密码授予客户端使用哪个用户提供者--provider 通过创建客户端时的选项artisan passport:client --password 命令。给定的提供者名称应与应用程序中定义的有效提供者相匹配config/auth.php 配置文件。然后你可以使用中间件保护你的路由 以确保只有警卫指定供应商的用户获得授权。

自定义用户名字段

使用密码授予进行身份验证时,Passport 将使用email 您的可验证模型的属性作为“用户名”。但是,您可以通过定义一个findForPassport 模型上的方法:

<?php

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Passport\HasApiTokens;

class User extends Authenticatable
{
    use HasApiTokens, Notifiable;

    /**
     * Find the user instance for the given username.
     */
    public function findForPassport(string $username): User
    {
        return $this->where('username', $username)->first();
    }
}

自定义密码验证

使用密码授予进行身份验证时,Passport 将使用password 模型的属性来验证给定的密码。如果您的型号没有password 属性或者您希望自定义密码验证逻辑,您可以定义一个validateForPassportPasswordGrant 模型上的方法:

<?php

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Hash;
use Laravel\Passport\HasApiTokens;

class User extends Authenticatable
{
    use HasApiTokens, Notifiable;

    /**
     * Validate the password of the user for the Passport password grant.
     */
    public function validateForPassportPasswordGrant(string $password): bool
    {
        return Hash::check($password, $this->password);
    }
}

隐式授予令牌

Warning
我们不再建议使用隐式授权令牌。相反,你应该选择OAuth2 服务器当前推荐的授权类型.

隐式授权类似于授权码授权;但是,令牌会在不交换授权代码的情况下返回给客户端。此授权最常用于无法安全存储客户端凭据的 JavaScript 或移动应用程序。要启用授权,请调用enableImplicitGrant 中的方法boot 你的应用程序的方法App\Providers\AuthServiceProvider 班级:

/**
 * Register any authentication / authorization services.
 */
public function boot(): void
{
    Passport::enableImplicitGrant();
}

启用授权后,开发人员可以使用他们的客户端 ID 从您的应用程序请求访问令牌。消费应用程序应向您的应用程序发出重定向请求/oauth/authorize像这样的路线:

use Illuminate\Http\Request;

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' => 'token',
        'scope' => '',
        'state' => $state,
        // 'prompt' => '', // "none", "consent", or "login"
    ]);

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

Note
请记住,/oauth/authorize 路线已由 Passport 定义。您不需要手动定义此路由。

客户端凭证授予令牌

客户端凭据授予适用于机器对机器的身份验证。例如,您可以在通过 API 执行维护任务的计划作业中使用此授权。

在您的应用程序可以通过客户端凭据授予颁发令牌之前,您需要创建一个客户端凭据授予客户端。您可以使用--client 的选项passport:client 工匠命令:

php artisan passport:client --client

接下来,要使用此授权类型,您可以添加CheckClientCredentials 中间件到$middlewareAliases 您的应用程序的属性app/Http/Kernel.php 文件:

use Laravel\Passport\Http\Middleware\CheckClientCredentials;

protected $middlewareAliases = [
    'client' => CheckClientCredentials::class,
];

然后,将中间件附加到路由:

Route::get('/orders', function (Request $request) {
    ...
})->middleware('client');

要将对路由的访问限制在特定范围内,您可以在附加时提供所需范围的逗号分隔列表client 路由的中间件:

Route::get('/orders', function (Request $request) {
    ...
})->middleware('client:check-status,your-scope');

检索令牌

要使用此授权类型检索令牌,请向oauth/token 端点:

use Illuminate\Support\Facades\Http;

$response = Http::asForm()->post('http://passport-app.test/oauth/token', [
    'grant_type' => 'client_credentials',
    'client_id' => 'client-id',
    'client_secret' => 'client-secret',
    'scope' => 'your-scope',
]);

return $response->json()['access_token'];

个人访问令牌

有时,您的用户可能希望在不经过典型的授权代码重定向流程的情况下向自己颁发访问令牌。允许用户通过您的应用程序的 UI 向自己颁发令牌对于允许用户试验您的 API 非常有用,或者可以作为一般情况下颁发访问令牌的更简单方法。

Note
如果您的应用程序主要使用 Passport 来颁发个人访问令牌,请考虑使用Laravel 圣殿,Laravel 的轻量级第一方库,用于发布 API 访问令牌。

创建个人访问客户端

在您的应用程序可以颁发个人访问令牌之前,您需要创建一个个人访问客户端。您可以通过执行passport:client 工匠命令与--personal 选项。如果您已经运行passport:install 命令,您不需要运行此命令:

php artisan passport:client --personal

创建您的个人访问客户端后,将客户端的 ID 和纯文本秘密值放入您的应用程序的.env 文件:

PASSPORT_PERSONAL_ACCESS_CLIENT_ID="client-id-value"
PASSPORT_PERSONAL_ACCESS_CLIENT_SECRET="unhashed-client-secret-value"

管理个人访问令牌

创建个人访问客户端后,您可以使用createToken 上的方法App\Models\User 模型实例。这createToken 方法接受令牌的名称作为它的第一个参数和一个可选的数组scopes 作为它的第二个参数:

use App\Models\User;

$user = User::find(1);

// Creating a token without scopes...
$token = $user->createToken('Token Name')->accessToken;

// Creating a token with scopes...
$token = $user->createToken('My Token', ['place-orders'])->accessToken;

JSON API

Passport 还包括一个用于管理个人访问令牌的 JSON API。您可以将其与您自己的前端配对,为您的用户提供一个用于管理个人访问令牌的仪表板。下面,我们将回顾所有用于管理个人访问令牌的 API 端点。为了方便,我们将使用Axios 演示向端点发出 HTTP 请求。

JSON API 由webauth 中间件;因此,它只能从您自己的应用程序中调用。无法从外部源调用它。

GET /oauth/scopes

这条路线返回所有scopes 为您的应用程序定义。您可以使用此路由列出用户可以分配给个人访问令牌的范围:

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

GET /oauth/personal-access-tokens

此路由返回经过身份验证的用户创建的所有个人访问令牌。这主要用于列出所有用户的令牌,以便他们可以编辑或撤销它们:

axios.get('/oauth/personal-access-tokens')
    .then(response => {
        console.log(response.data);
    });

POST /oauth/personal-access-tokens

此路由创建新的个人访问令牌。它需要两条数据:令牌的namescopes 应该分配给令牌:

const data = {
    name: 'Token Name',
    scopes: []
};

axios.post('/oauth/personal-access-tokens', data)
    .then(response => {
        console.log(response.data.accessToken);
    })
    .catch (response => {
        // List errors on response...
    });

DELETE /oauth/personal-access-tokens/{token-id}

此路由可用于撤销个人访问令牌:

axios.delete('/oauth/personal-access-tokens/' + tokenId);

保护路线

通过中间件

护照包括鉴权守卫 这将验证传入请求的访问令牌。一旦你配置了api 守卫使用passport 驱动程序,你只需要指定auth:api 任何需要有效访问令牌的路由上的中间件:

Route::get('/user', function () {
    // ...
})->middleware('auth:api');

Warning
如果您正在使用客户凭据授予,你应该使用client 中间件 保护你的路线而不是auth:api 中间件。

多重认证守卫

如果您的应用程序对可能使用完全不同的 Eloquent 模型的不同类型的用户进行身份验证,您可能需要为应用程序中的每个用户提供者类型定义一个保护配置。这使您可以保护针对特定用户提供商的请求。例如,给定以下守卫配置config/auth.php配置文件:

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

'api-customers' => [
    'driver' => 'passport',
    'provider' => 'customers',
],

以下路线将利用api-customers 守卫,它使用customers 用户提供者,对传入请求进行身份验证:

Route::get('/customer', function () {
    // ...
})->middleware('auth:api-customers');

Note
有关通过 Passport 使用多个用户提供商的更多信息,请参阅密码授予文档.

传递访问令牌

当调用受 Passport 保护的路由时,应用程序的 API 使用者应将其访问令牌指定为Bearer 令牌在Authorization 他们请求的标题。例如,当使用 Guzzle HTTP 库时:

use Illuminate\Support\Facades\Http;

$response = Http::withHeaders([
    'Accept' => 'application/json',
    'Authorization' => 'Bearer '.$accessToken,
])->get('https://passport-app.test/api/user');

return $response->json();

令牌范围

范围允许您的 API 客户端在请求访问帐户的授权时请求一组特定的权限。例如,如果您正在构建电子商务应用程序,则并非所有 API 消费者都需要下订单的能力。相反,您可以允许消费者仅请求访问订单发货状态的授权。换句话说,范围允许您的应用程序的用户限制第三方应用程序可以代表他们执行的操作。

定义范围

您可以使用Passport::tokensCan 中的方法boot 你的应用程序的方法App\Providers\AuthServiceProvider 班级。这tokensCan 方法接受范围名称和范围描述的数组。范围描述可以是任何你想要的,将在授权批准屏幕上显示给用户:

/**
 * Register any authentication / authorization services.
 */
public function boot(): void
{
    Passport::tokensCan([
        'place-orders' => 'Place orders',
        'check-status' => 'Check order status',
    ]);
}

默认范围

如果客户端不请求任何特定范围,您可以配置您的 Passport 服务器以使用以下方法将默认范围附加到令牌setDefaultScope 方法。通常,您应该从boot 你的应用程序的方法App\Providers\AuthServiceProvider 班级:

use Laravel\Passport\Passport;

Passport::tokensCan([
    'place-orders' => 'Place orders',
    'check-status' => 'Check order status',
]);

Passport::setDefaultScope([
    'check-status',
    'place-orders',
]);

Note Passport 的默认范围不适用于用户生成的个人访问令牌。

为令牌分配范围

请求授权码时

当使用授权代码授予请求访问令牌时,消费者应将他们想要的范围指定为scope 查询字符串参数。这scope 参数应该是以空格分隔的范围列表:

Route::get('/redirect', function () {
    $query = http_build_query([
        'client_id' => 'client-id',
        'redirect_uri' => 'http://example.com/callback',
        'response_type' => 'code',
        'scope' => 'place-orders check-status',
    ]);

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

颁发个人访问令牌时

如果您使用App\Models\User 楷模createToken 方法,您可以将所需范围的数组作为第二个参数传递给该方法:

$token = $user->createToken('My Token', ['place-orders'])->accessToken;

检查范围

Passport 包括两个中间件,可用于验证传入请求是否已使用已授予给定范围的令牌进行身份验证。首先,将以下中间件添加到$middlewareAliases 你的财产app/Http/Kernel.php 文件:

'scopes' => \Laravel\Passport\Http\Middleware\CheckScopes::class,
'scope' => \Laravel\Passport\Http\Middleware\CheckForAnyScope::class,

检查所有范围

scopes 可以将中间件分配给路由以验证传入请求的访问令牌是否具有所有列出的范围:

Route::get('/orders', function () {
    // Access token has both "check-status" and "place-orders" scopes...
})->middleware(['auth:api', 'scopes:check-status,place-orders']);

检查任何范围

scope 可以将中间件分配给路由以验证传入请求的访问令牌是否具有最后一个 列出的范围:

Route::get('/orders', function () {
    // Access token has either "check-status" or "place-orders" scope...
})->middleware(['auth:api', 'scope:check-status,place-orders']);

检查令牌实例的范围

一旦访问令牌验证请求进入您的应用程序,您仍然可以使用检查令牌是否具有给定范围tokenCan 认证方法App\Models\User 实例:

use Illuminate\Http\Request;

Route::get('/orders', function (Request $request) {
    if ($request->user()->tokenCan('place-orders')) {
        // ...
    }
});

其他作用域方法

scopeIds 方法将返回所有定义的 ID/名称的数组:

use Laravel\Passport\Passport;

Passport::scopeIds();

scopes 方法将返回所有定义范围的数组作为实例Laravel\Passport\Scope:

Passport::scopes();

scopesFor 方法将返回一个数组Laravel\Passport\Scope 与给定 ID/名称匹配的实例:

Passport::scopesFor(['place-orders', 'check-status']);

您可以使用hasScope 方法:

Passport::hasScope('place-orders');

使用 JavaScript 使用您的 API

在构建 API 时,能够从 JavaScript 应用程序使用您自己的 API 会非常有用。这种 API 开发方法允许您自己的应用程序使用您与世界共享的相同 API。您的 Web 应用程序、移动应用程序、第三方应用程序以及您可能在各种包管理器上发布的任何 SDK 可能会使用相同的 API。

通常,如果您想从 JavaScript 应用程序使用 API,则需要手动向应用程序发送访问令牌,并将其与每个请求一起传递给您的应用程序。但是,Passport 包含一个可以为您处理此问题的中间件。您需要做的就是添加CreateFreshApiToken 中间件到你的web 你的中间件组app/Http/Kernel.php文件:

'web' => [
    // Other middleware...
    \Laravel\Passport\Http\Middleware\CreateFreshApiToken::class,
],

Warning
你应该确保CreateFreshApiToken 中间件是中间件堆栈中列出的最后一个中间件。

这个中间件将附加一个laravel_token cookie 到您的外发回复。此 cookie 包含一个加密的 JWT,Passport 将使用它来验证来自您的 JavaScript 应用程序的 API 请求。 JWT 的生命周期等于你的session.lifetime 配置值。现在,由于浏览器会自动发送带有所有后续请求的 cookie,您可以在不显式传递访问令牌的情况下向应用程序的 API 发出请求:

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

自定义 Cookie 名称

如果需要,您可以自定义laravel_token cookie 的名称使用Passport::cookie 方法。通常,应从boot 你的应用程序的方法App\Providers\AuthServiceProvider 班级:

/**
 * Register any authentication / authorization services.
 */
public function boot(): void
{
    Passport::cookie('custom_name');
}

CSRF保护

使用这种身份验证方法时,您需要确保请求中包含有效的 CSRF 令牌标头。默认的 Laravel JavaScript 脚手架包含一个 Axios 实例,它将自动使用加密的XSRF-TOKEN 要发送的 cookie 值X-XSRF-TOKEN 同源请求的标头。

Note
如果您选择发送X-CSRF-TOKEN 标题而不是X-XSRF-TOKEN,您将需要使用由csrf_token().

Events

Passport 在颁发访问令牌和刷新令牌时引发事件。您可以使用这些事件来修剪或撤销数据库中的其他访问令牌。如果愿意,您可以在应用程序的App\Providers\EventServiceProvider 班级:

/**
 * The event listener mappings for the application.
 *
 * @var array
 */
protected $listen = [
    'Laravel\Passport\Events\AccessTokenCreated' => [
        'App\Listeners\RevokeOldTokens',
    ],

    'Laravel\Passport\Events\RefreshTokenCreated' => [
        'App\Listeners\PruneOldTokens',
    ],
];

Testing

护照的actingAs 方法可用于指定当前经过身份验证的用户及其范围。给的第一个参数actingAs 方法是用户实例,第二个是应该授予用户令牌的范围数组:

use App\Models\User;
use Laravel\Passport\Passport;

public function test_servers_can_be_created(): void
{
    Passport::actingAs(
        User::factory()->create(),
        ['create-servers']
    );

    $response = $this->post('/api/create-server');

    $response->assertStatus(201);
}

护照的actingAsClient 方法可用于指定当前经过身份验证的客户端及其范围。给的第一个参数actingAsClient 方法是客户端实例,第二个是应该授予客户端令牌的范围数组:

use Laravel\Passport\Client;
use Laravel\Passport\Passport;

public function test_orders_can_be_retrieved(): void
{
    Passport::actingAsClient(
        Client::factory()->create(),
        ['check-status']
    );

    $response = $this->get('/api/orders');

    $response->assertStatus(200);
}
豫ICP备18041297号-2