Skip to content
赞助商
虚位以待
赞助商
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待

API 认证 (Passport)

介绍

Laravel 已经使通过传统登录表单进行身份验证变得简单,但对于 API 呢?API 通常使用令牌来验证用户,并且在请求之间不维护会话状态。Laravel 使用 Laravel Passport 使 API 认证变得轻而易举,Passport 为您的 Laravel 应用程序提供了完整的 OAuth2 服务器实现,只需几分钟即可完成。Passport 构建在由 Alex Bilbie 维护的 League OAuth2 server 之上。

NOTE

本文档假设您已经熟悉 OAuth2。如果您对 OAuth2 一无所知,请考虑在继续之前先熟悉 OAuth2 的一般术语和功能。

安装

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

php
composer require laravel/passport=~1.0

接下来,在 config/app.php 配置文件的 providers 数组中注册 Passport 服务提供者:

php
Laravel\Passport\PassportServiceProvider::class,

Passport 服务提供者会将其自己的数据库迁移目录注册到框架中,因此在注册提供者后,您应该迁移数据库。Passport 迁移将创建您的应用程序需要的表来存储客户端和访问令牌:

php
php artisan migrate

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

php
php artisan passport:install

运行此命令后,将 Laravel\Passport\HasApiTokens trait 添加到您的 App\User 模型中。此 trait 将为您的模型提供一些辅助方法,允许您检查经过身份验证的用户的令牌和范围:

php
<?php

namespace App;

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

class User extends Authenticatable
{
    use HasApiTokens, Notifiable;
}

接下来,您应该在 AuthServiceProviderboot 方法中调用 Passport::routes 方法。此方法将注册颁发访问令牌和撤销访问令牌、客户端和个人访问令牌所需的路由:

php
<?php

namespace App\Providers;

use Laravel\Passport\Passport;
use Illuminate\Support\Facades\Gate;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;

class AuthServiceProvider extends ServiceProvider
{
    /**
     * 应用程序的策略映射。
     *
     * @var array
     */
    protected $policies = [
        'App\Model' => 'App\Policies\ModelPolicy',
    ];

    /**
     * 注册任何身份验证/授权服务。
     *
     * @return void
     */
    public function boot()
    {
        $this->registerPolicies();

        Passport::routes();
    }
}

最后,在 config/auth.php 配置文件中,您应该将 api 身份验证守卫的 driver 选项设置为 passport。这将指示您的应用程序在验证传入的 API 请求时使用 Passport 的 TokenGuard

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

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

前端快速入门

NOTE

为了使用 Passport Vue 组件,您必须使用 Vue JavaScript 框架。这些组件还使用 Bootstrap CSS 框架。但是,即使您不使用这些工具,这些组件也可以作为您自己前端实现的有价值的参考。

Passport 附带一个 JSON API,您可以使用它来允许用户创建客户端和个人访问令牌。然而,编写一个与这些 API 交互的前端可能会耗费时间。因此,Passport 还包括预构建的 Vue 组件,您可以将其用作示例实现或您自己实现的起点。

要发布 Passport Vue 组件,请使用 vendor:publish Artisan 命令:

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

发布的组件将放置在您的 resources/assets/js/components 目录中。发布组件后,您应该在 resources/assets/js/app.js 文件中注册它们:

php
Vue.component(
    'passport-clients',
    require('./components/passport/Clients.vue')
);

Vue.component(
    'passport-authorized-clients',
    require('./components/passport/AuthorizedClients.vue')
);

Vue.component(
    'passport-personal-access-tokens',
    require('./components/passport/PersonalAccessTokens.vue')
);

注册组件后,请确保运行 gulp 以重新编译您的资产。重新编译资产后,您可以将组件放入应用程序的模板之一中,以开始创建客户端和个人访问令牌:

php
<passport-clients></passport-clients>
<passport-authorized-clients></passport-authorized-clients>
<passport-personal-access-tokens></passport-personal-access-tokens>

配置

令牌有效期

默认情况下,Passport 颁发的访问令牌是长期有效的,不需要刷新。如果您希望配置较短的令牌有效期,可以使用 tokensExpireInrefreshTokensExpireIn 方法。这些方法应在 AuthServiceProviderboot 方法中调用:

php
use Carbon\Carbon;

/**
 * 注册任何身份验证/授权服务。
 *
 * @return void
 */
public function boot()
{
    $this->registerPolicies();

    Passport::routes();

    Passport::tokensExpireIn(Carbon::now()->addDays(15));

    Passport::refreshTokensExpireIn(Carbon::now()->addDays(30));
}

颁发访问令牌

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

管理客户端

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

passport:client 命令

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

php
php artisan passport:client

JSON API

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

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

NOTE

如果您不想自己实现整个客户端管理前端,可以使用 前端快速入门 在几分钟内拥有一个功能齐全的前端。

GET /oauth/clients

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

php
this.$http.get('/oauth/clients')
    .then(response => {
        console.log(response.data);
    });

POST /oauth/clients

此路由用于创建新客户端。它需要两条数据:客户端的 name 和一个 redirect URL。redirect URL 是用户在批准或拒绝授权请求后将被重定向到的地方。

创建客户端时,将为其分配一个客户端 ID 和客户端密钥。这些值将在从您的应用程序请求访问令牌时使用。客户端创建路由将返回新的客户端实例:

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

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

PUT /oauth/clients/{client-id}

此路由用于更新客户端。它需要两条数据:客户端的 name 和一个 redirect URL。redirect URL 是用户在批准或拒绝授权请求后将被重定向到的地方。该路由将返回更新后的客户端实例:

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

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

DELETE /oauth/clients/{client-id}

此路由用于删除客户端:

php
this.$http.delete('/oauth/clients/' + clientId)
    .then(response => {
        //
    });

请求令牌

重定向以获取授权

一旦创建了客户端,开发人员可以使用他们的客户端 ID 和密钥从您的应用程序请求授权码和访问令牌。首先,消费应用程序应向您的应用程序的 /oauth/authorize 路由发出重定向请求,如下所示:

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

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

NOTE

请记住,/oauth/authorize 路由已由 Passport::routes 方法定义。您不需要手动定义此路由。

批准请求

在接收授权请求时,Passport 将自动向用户显示一个模板,允许他们批准或拒绝授权请求。如果他们批准请求,他们将被重定向回消费应用程序指定的 redirect_uriredirect_uri 必须与创建客户端时指定的 redirect URL 匹配。

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

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

将授权码转换为访问令牌

如果用户批准了授权请求,他们将被重定向回消费应用程序。然后,消费者应向您的应用程序发出 POST 请求以请求访问令牌。请求应包括用户批准授权请求时由您的应用程序颁发的授权码。在此示例中,我们将使用 Guzzle HTTP 库发出 POST 请求:

php
Route::get('/callback', function (Request $request) {
    $http = new GuzzleHttp\Client;

    $response = $http->post('http://your-app.com/oauth/token', [
        'form_params' => [
            'grant_type' => 'authorization_code',
            'client_id' => 'client-id',
            'client_secret' => 'client-secret',
            'redirect_uri' => 'http://example.com/callback',
            'code' => $request->code,
        ],
    ]);

    return json_decode((string) $response->getBody(), true);
});

/oauth/token 路由将返回一个包含 access_tokenrefresh_tokenexpires_in 属性的 JSON 响应。expires_in 属性包含访问令牌过期的秒数。

NOTE

/oauth/authorize 路由一样,/oauth/token 路由由 Passport::routes 方法为您定义。无需手动定义此路由。

刷新令牌

如果您的应用程序颁发短期访问令牌,用户将需要通过在颁发访问令牌时提供的刷新令牌来刷新他们的访问令牌。在此示例中,我们将使用 Guzzle HTTP 库刷新令牌:

php
$http = new GuzzleHttp\Client;

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

return json_decode((string) $response->getBody(), true);

/oauth/token 路由将返回一个包含 access_tokenrefresh_tokenexpires_in 属性的 JSON 响应。expires_in 属性包含访问令牌过期的秒数。

密码授权令牌

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

创建密码授权客户端

在您的应用程序可以通过密码授权颁发令牌之前,您需要创建一个密码授权客户端。您可以使用带有 --password 选项的 passport:client 命令来执行此操作。如果您已经运行了 passport:install 命令,则无需运行此命令:

php
php artisan passport:client --password

请求令牌

一旦您创建了密码授权客户端,您可以通过向 /oauth/token 路由发出 POST 请求并提供用户的电子邮件地址和密码来请求访问令牌。请记住,此路由已由 Passport::routes 方法注册,因此无需手动定义。如果请求成功,您将收到服务器返回的 JSON 响应中的 access_tokenrefresh_token

php
$http = new GuzzleHttp\Client;

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

return json_decode((string) $response->getBody(), true);

NOTE

请记住,访问令牌默认是长期有效的。但是,如果需要,您可以自由地配置您的最大访问令牌有效期

请求所有范围

使用密码授权时,您可能希望为应用程序支持的所有范围授权令牌。您可以通过请求 * 范围来实现这一点。如果您请求 * 范围,令牌实例上的 can 方法将始终返回 true。此范围只能分配给使用 password 授权颁发的令牌:

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

隐式授权令牌

隐式授权类似于授权码授权;然而,令牌在不交换授权码的情况下返回给客户端。此授权最常用于 JavaScript 或移动应用程序,其中客户端凭证无法安全存储。要启用授权,请在 AuthServiceProvider 中调用 enableImplicitGrant 方法:

php
/**
 * 注册任何身份验证/授权服务。
 *
 * @return void
 */
public function boot()
{
    $this->registerPolicies();

    Passport::routes();

    Passport::enableImplicitGrant();
}

一旦启用了授权,开发人员可以使用他们的客户端 ID 从您的应用程序请求访问令牌。消费应用程序应向您的应用程序的 /oauth/authorize 路由发出重定向请求,如下所示:

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

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

NOTE

请记住,/oauth/authorize 路由已由 Passport::routes 方法定义。您不需要手动定义此路由。

客户端凭证授权令牌

客户端凭证授权适用于机器对机器的身份验证。例如,您可以在执行 API 维护任务的计划任务中使用此授权。要检索令牌,请向 oauth/token 端点发出请求:

php
$guzzle = new GuzzleHttp\Client;

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

echo json_decode((string) $response->getBody(), true);

个人访问令牌

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

NOTE

个人访问令牌始终是长期有效的。使用 tokensExpireInrefreshTokensExpireIn 方法时,它们的有效期不会被修改。

创建个人访问客户端

在您的应用程序可以颁发个人访问令牌之前,您需要创建一个个人访问客户端。您可以使用带有 --personal 选项的 passport:client 命令来执行此操作。如果您已经运行了 passport:install 命令,则无需运行此命令:

php
php artisan passport:client --personal

管理个人访问令牌

一旦您创建了个人访问客户端,您可以使用 User 模型实例上的 createToken 方法为给定用户颁发令牌。createToken 方法接受令牌的名称作为其第一个参数,并接受一个可选的范围数组作为其第二个参数:

php
$user = App\User::find(1);

// 创建没有范围的令牌...
$token = $user->createToken('Token Name')->accessToken;

// 创建具有范围的令牌...
$token = $user->createToken('My Token', ['place-orders'])->accessToken;

JSON API

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

NOTE

如果您不想自己实现个人访问令牌前端,可以使用 前端快速入门 在几分钟内拥有一个功能齐全的前端。

GET /oauth/scopes

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

php
this.$http.get('/oauth/scopes')
    .then(response => {
        console.log(response.data);
    });

GET /oauth/personal-access-tokens

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

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

POST /oauth/personal-access-tokens

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

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

this.$http.post('/oauth/personal-access-tokens', data)
    .then(response => {
        console.log(response.data.accessToken);
    })
    .catch (response => {
        // 列出响应中的错误...
    });

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

此路由可用于删除个人访问令牌:

php
this.$http.delete('/oauth/personal-access-tokens/' + tokenId);

保护路由

通过中间件

Passport 包含一个身份验证守卫,它将在传入请求上验证访问令牌。一旦您将 api 守卫配置为使用 passport 驱动程序,您只需在任何需要有效访问令牌的路由上指定 auth:api 中间件:

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

传递访问令牌

在调用由 Passport 保护的路由时,您的应用程序的 API 消费者应在请求的 Authorization 头中将其访问令牌指定为 Bearer 令牌。例如,使用 Guzzle HTTP 库时:

php
$response = $client->request('GET', '/api/user', [
    'headers' => [
        'Accept' => 'application/json',
        'Authorization' => 'Bearer '.$accessToken,
    ],
]);

令牌范围

定义范围

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

您可以在 AuthServiceProviderboot 方法中使用 Passport::tokensCan 方法定义 API 的范围。tokensCan 方法接受一个范围名称和范围描述的数组。范围描述可以是您希望的任何内容,并将在授权批准屏幕上显示给用户:

php
use Laravel\Passport\Passport;

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

分配范围给令牌

请求授权码时

在使用授权码授权请求访问令牌时,消费者应将其所需的范围指定为 scope 查询字符串参数。scope 参数应为一个以空格分隔的范围列表:

php
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://your-app.com/oauth/authorize?'.$query);
});

颁发个人访问令牌时

如果您正在使用 User 模型的 createToken 方法颁发个人访问令牌,您可以将所需范围的数组作为方法的第二个参数传递:

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

检查范围

Passport 包含两个中间件,可用于验证传入请求是否使用已授予给定范围的令牌进行身份验证。要开始,请将以下中间件添加到 app/Http/Kernel.php 文件的 $routeMiddleware 属性中:

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

检查所有范围

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

php
Route::get('/orders', function () {
    // 访问令牌具有 "check-status" 和 "place-orders" 范围...
})->middleware('scopes:check-status,place-orders');

检查任何范围

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

php
Route::get('/orders', function () {
    // 访问令牌具有 "check-status" 或 "place-orders" 范围...
})->middleware('scope:check-status,place-orders');

在令牌实例上检查范围

一旦经过身份验证的请求进入您的应用程序,您仍然可以使用经过身份验证的 User 实例上的 tokenCan 方法检查令牌是否具有给定范围:

php
use Illuminate\Http\Request;

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

使用 JavaScript 消费 API

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

通常,如果您想从 JavaScript 应用程序中消费 API,您需要手动将访问令牌发送到应用程序,并在每个请求中传递给您的应用程序。然而,Passport 包含一个中间件可以为您处理这一点。您只需将 CreateFreshApiToken 中间件添加到您的 web 中间件组中:

php
'web' => [
    // 其他中间件...
    \Laravel\Passport\Http\Middleware\CreateFreshApiToken::class,
],

此 Passport 中间件将向您的传出响应附加一个 laravel_token cookie。此 cookie 包含一个加密的 JWT,Passport 将使用它来验证来自您的 JavaScript 应用程序的 API 请求。现在,您可以在不显式传递访问令牌的情况下向应用程序的 API 发出请求:

php
this.$http.get('/user')
    .then(response => {
        console.log(response.data);
    });

使用此身份验证方法时,您需要通过 X-CSRF-TOKEN 头发送 CSRF 令牌。Laravel 将自动发送此头,如果您使用的是框架附带的默认 Vue 配置:

php
Vue.http.interceptors.push((request, next) => {
    request.headers.set('X-CSRF-TOKEN', Laravel.csrfToken);

    next();
});

NOTE

如果您使用的是不同的 JavaScript 框架,您应该确保它配置为在每个传出请求中发送此头。

事件

Passport 在颁发访问令牌和刷新令牌时会触发事件。您可以使用这些事件来修剪或撤销数据库中的其他访问令牌。您可以在应用程序的 EventServiceProvider 中附加这些事件的监听器:

php
/**
 * 应用程序的事件监听器映射。
 *
 * @var array
 */
protected $listen = [
    'Laravel\Passport\Events\AccessTokenCreated' => [
        'App\Listeners\RevokeOldTokens',
    ],

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