Laravel 的 API 认证系统 Passport
介绍
在 Laravel 中,实现基于传统表单的登陆和授权已经非常简单,但是如何满足 API 场景下的授权需求呢?在 API 场景里通常通过令牌来实现用户授权,而非维护请求之间的 Session 状态。现在 Laravel 项目中可以使用 Passport 轻而易举地实现 API 授权过程,通过 Passport 可以在几分钟之内为你的应用程序添加完整的 OAuth2 服务端实现。 Passport 基于 League OAuth2 server 实现,该项目的维护人是 Alex Bilbie 。
本文档假定你已熟悉 OAuth2 。如果你并不了解 OAuth2 ,阅读之前请先熟悉下 OAuth2 的常用术语和基本特征。
安装
使用 Composer 依赖包管理器安装 Passport :
composer require laravel/passport
接下来,将 Passport 的服务提供者注册到配置文件 config/app.php
的 providers
数组中:
Laravel\Passport\PassportServiceProvider::class,
Passport 使用服务提供者注册内部的数据库迁移脚本目录,所以上一步完成后,你需要更新你的数据库结构。Passport 的迁移脚本会自动创建应用程序需要的客户端数据表和令牌数据表:
php artisan migrate
如果你不打算使用 Passport 的默认迁移,你应该在AppServiceProvider
的register
方法中调用Passport :: ignoreMigrations
方法。 你可以导出这个默认迁移用php artisan vendor:publish --tag=passport-migrations
命令。
接下来,你需要运行 passport:install
命令来创建生成安全访问令牌时用到的加密密钥,同时,这条命令也会创建「私人访问」客户端和「密码授权」客户端:
php artisan passport:install
上面命令执行后,请将 Laravel\Passport\HasApiTokens
Trait 添加到 App\User
模型中,这个 Trait 会给你的模型提供一些辅助函数,用于检查已认证用 户的令牌和使用作用域:
<?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;
}
接下来,需要在 AuthServiceProvider
的 boot
方法中调用 Passport::routes
函数。这个函数会注册一些在访问令牌、客户端、私人访问令牌的发放和吊销过程中会用到的必要路由:
<?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
{
/**
* The policy mappings for the application.
*
* @var array
*/
protected $policies = [
'App\Model' => 'App\Policies\ModelPolicy',
];
/**
* Register any authentication / authorization services.
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
Passport::routes();
}
}
最后,需要将配置文件 config/auth.php
中 api
部分的授权保护项( driver
)改为 passport
。此调整会让你的应用程序在接收到 API 的授权请求时使用 Passport 的 TokenGuard
来处理:
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'passport',
'provider' => 'users',
],
],
前端快速上手
如果想要使用 Passport 的 Vue 组件,那么你必须使用 Vue Javascript 框架,另外这些组件还用到了 Bootstrap CSS 框架。当然你也可以不使用上面的任何工具,但在实现你自己的前端部分时,Passport 的 Vue 组件仍旧有很高的参考价值。
Passport 配备了一些可以让你的用户自行创建客户端和私人访问令牌的 JSON API。所以,你可以自己花费时间来编写一些前端代码来使用这些 API。当然在 Passport 中也已经预制了一些 Vue 组件,你可以直接使用这些示例代码,也可以基于这些代码实现自己的前端部分。
使用 Artisan 命令 vendor:publish
来发布 Passport 的 Vue 组件:
php artisan vendor:publish --tag=passport-components
已发布的组件将被放置在 resources/assets/js/components
目录中,可以在 resources/assets/js/app.js
文件中注册这些已发布的组件:
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')
);
这些组件注册后,你可以直接将这些组件直接放入应用程序的模板中,用于创建客户端和私人访问令牌:
<passport-clients></passport-clients>
<passport-authorized-clients></passport-authorized-clients>
<passport-personal-access-tokens></passport-personal-access-tokens>
配置
令牌的有效期
默认情况下,Passport 发放的访问令牌是永久有效的,不需要刷新。但是如果你想给访问令牌配置一个短一些的有效期,那你就需要用到 tokensExpireIn
和 refreshTokensExpireIn
方法了,上述两个方法同样需要在 AuthServiceProvider
的 boot
方法中调用:
use Carbon\Carbon;
/**
* Register any authentication / authorization services.
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
Passport::routes();
Passport::tokensExpireIn(Carbon::now()->addDays(15));
Passport::refreshTokensExpireIn(Carbon::now()->addDays(30));
}
发放访问令牌
熟悉 OAuth2 的开发者一定知道, OAuth2 中必不可少的部分就是授权码。在获取授权码时,接入应用会重定向一个用户到你的服务端,用户可以选择允许或拒绝向这个客户端发放访问令牌。
管理客户端
首先,接入应用如果想要与你应用的 API 进行交互,必须先在你的应用程序中注册一个「客户端」。一般来说,这个注册过程需要开发者提供两部分信息:接入应用名称和用户授权后的跳转链接。
命令 passport:client
创建客户端最简单的方式是使用 Artisan 命令 passport:client
,你可以使用此命令创建自己的客户端,用于测试 OAuth2 的功能。在你执行 client
命令时,Passport 会提示输入更多关于你的客户端的信息,最终会提供给你生成的客户端的 ID 和 密钥:
php artisan passport:client
JSON API
考虑到你的用户们并没有办法使用 client
命令,Passport 同时提供了用户创建客户端的 JSON API 。这样你就不用再花时间编码来实现客户端创建、更新和删除的相关控制器逻辑了。
然而,你仍旧需要基于 Passport 的 JSON API 开发一套前端界面,方便你的用户管理他们授权的客户端。下面我们会列出所有用于管理客户端的 API,方便起见,我们使用 Vue 展示对 API 的 HTTP 请求。
如果你不想自己重写整个客户端管理的前端界面,可以根据 前端快速上手 在几分钟内组建一套功能完备的前端界面。
GET /oauth/clients
此接口会返回当前认证用户的所有客户端。主要用途是列出当前用户所有客户端,方便用户修改或删除:
this.$http.get('/oauth/clients')
.then(response => {
console.log(response.data);
});
POST /oauth/clients
此接口用户创建新的客户端。它需要两部分数据:客户端的名称、客户端的 redirect
链接。当用户允许或拒绝授权请求后,用户都会被重定向到这个 redirect
链接。
当客户端创建完成后,会生成此客户端的 ID 和密钥,客户端可以使用这两个值从你的应用程序请求访问令牌。此接口会返回新建客户端实例的信息:
const data = {
name: 'Client Name',
redirect: 'http://example.com/callback'
};
this.$http.post('/oauth/clients', data)
.then(response => {
console.log(response.data);
})
.catch (response => {
// List errors on response...
});
PUT /oauth/clients/{client-id}
此接口用于更新客户端信息。它需要两部分数据:客户端的名称和 redirect
链接。当用户允许或拒绝授权请求后,用户都会被重定向到这个 redirect
链接。此接口会返回被更新客户端实例的信息:
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 => {
// List errors on response...
});
DELETE /oauth/clients/{client-id}
此接口用于删除客户端:
axios.delete('/oauth/clients/' + clientId)
.then(response => {
//
});
请求令牌
授权时的重定向
客户端创建之后,开发者会使用此客户端的 ID 和密钥向你的应用程序请求一个授权码和访问令牌。首先,接入应用会将用户重定向到你应用程序的 /oauth/authorize
路由上,示例如下:
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);
});
注意,路由 /oauth/authorize
已经在 Passport::routes
方法中定义,所以无需再次定义。