Laravel API 认证:OAuth 认证
我们可以使用 laravel/passport
来提供OAuth 认证服务。
安装
在开始之前,请通过 Composer 包管理器安装 Passport:
composer require laravel/passport
Passport 服务提供器使用框架注册自己的数据库迁移目录,因此在注册提供器后,就应该运行 Passport 的迁移命令来自动创建存储客户端和令牌的数据表:
php artisan migrate
接下来,运行 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
{
/**
* 应用程序的策略映射。
*
* @var array
*/
protected $policies = [
'App\Model' => 'App\Policies\ModelPolicy',
];
/**
* 注册任何认证/授权服务。
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
Passport::routes();
}
}
最后,将配置文件 config/auth.php
中授权看守器 guards
的 api
的 driver
选项改为 passport
。此调整会让你的应用程序在在验证传入的 API 的请求时使用 Passport 的 TokenGuard
来处理:
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'passport',
'provider' => 'users',
],
],
自定义迁移
如果你不打算使用 Passport 的默认迁移,你应该在 AppServiceProvider
的 register
方法中调用 Passport::ignoreMigrations
方法。 你可以用这个命令 php artisan vendor:publish --tag=passport-migrations
导出默认迁移。
默认情况下,Passport 使用字段 「user_id」标识用户。如果想使用不同的字段标识用户 (例如:uuid),可以修改默认的 Passport 迁移文件。
前端快速上手
Passport 提供了一系列 JSON API ,你可以用它们来允许你的用户创建客户端和个人访问令牌。然而,编写与这些 API 交互的前端代码可能是很占用时间的。因此,Passport 也包括了预编译的 Vue 组件,你可以直接使用或将其作为你自己的前端参考。
要使用 Passport 的 Vue 组件,使用 vendor:publish
Artisan 命令:
php artisan vendor:publish --tag=passport-components
被发布的组件将会被放到 resources/js/components
目录下。当组件被发布后,你应该在你的 resources/js/app.js
文件中注册它们:
Vue.component(
'passport-clients',
require('./components/passport/Clients.vue').default
);
Vue.component(
'passport-authorized-clients',
require('./components/passport/AuthorizedClients.vue').default
);
Vue.component(
'passport-personal-access-tokens',
require('./components/passport/PersonalAccessTokens.vue').default
);
在注册了组件后,请确保运行 npm run dev
来重新编译你的资源。 当你重编译你的资源后,你可以将组件放到你应用的模板中以开始创建客户端和个人访问令牌:
<passport-clients></passport-clients>
<passport-authorized-clients></passport-authorized-clients>
<passport-personal-access-tokens></passport-personal-access-tokens>
部署 Passport
第一次在你的生产环境部署 Passport 时,你大概需要运行 passport:keys
命令。这个命令生成 Passport 生成访问令牌所需的密钥。生成的密钥一般情况下不应放在版本控制中:
php artisan passport:keys
可以使用 Passport::loadKeysFrom
方法来自定义 Passport 密钥的加载路径:
/**
* 注册认证 / 授权服务
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
Passport::routes();
Passport::loadKeysFrom('/secret-keys/oauth');
}
配置
令牌有效期
默认情况下,Passport 发放的访问令牌是有一年有效期的。但是如果你想自定义访问令牌的有效期,可以使用 tokensExpireIn
和 refreshTokensExpireIn
方法。上述两个方法同样需要在 AuthServiceProvider
的 boot
方法中调用:
/**
* 注册认证 / 授权服务
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
Passport::routes();
Passport::tokensExpireIn(now()->addDays(15));
Passport::refreshTokensExpireIn(now()->addDays(30));
}
覆盖默认模型
可以自由扩展 Passport 使用的模型,通过 Passport
类自定义模型覆盖默认模型:
use App\Models\Passport\Client;
use App\Models\Passport\AuthCode;
use App\Models\Passport\TokenModel;
use App\Models\Passport\PersonalAccessClient;
/**
* 注册认证 / 授权服务
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
Passport::routes();
Passport::useClientModel(Client::class);
Passport::useTokenModel(TokenModel::class);
Passport::useAuthCodeModel(AuthCode::class);
Passport::usePersonalAccessClientModel(PersonalAccessClient::class);
}
发放访问令牌
熟悉 OAuth2 的开发者一定知道, OAuth2 中必不可少的部分就是授权码。当使用授权码时,客户端应用程序会将用户重定向到你的服务器,他们将批准或拒绝向客户端发出访问令牌的请求。
管理客户端
首先,构建需要与应用程序 API 交互的应用程序,开发人员将需要通过创建一个「客户端」来注册自己的应用程序。一般来说,这包括在用户批准其授权请求后,提供其应用程序的名称和应用程序可以重定向到的 URL。
The passport:client
命令
创建客户端最简单的方式是使用 Artisan 命令 passport:client
,你可以使用此命令创建自己的客户端,用于测试你的 OAuth2 的功能。在你执行 client
命令时,Passport 会提示你输入有关客户端的信息,最终会给你提供客户端的 ID 和 密钥:
php artisan passport:client
Redirect URLs
当有多个重定向 URL 白名单时,可以在 passport:client
命令提示输入 URL 时,使用逗号分隔来指定:
http://example.com/callback,http://examplefoo.com/callback
密码授权令牌
OAuth2 密码授权机制可以让你自己的客户端(如移动应用程序)使用邮箱地址或者用户名和密码获取访问令牌。如此一来你就可以安全地向自己的客户端发出访问令牌,而不需要遍历整个 OAuth2 授权代码重定向流程。
在应用程序通过密码授权机制来发布令牌之前,在 passport:client
命令后加上 --password
参数来创建密码授权的客户端。如果你已经运行了 passport:install
命令,则不需要再运行此命令:
php artisan passport:client --password
请求令牌
创建密码授权的客户端后,就可以使用用户的电子邮件地址和密码向 /oauth/token
路由发出 POST
请求来获取访问令牌。而该路由已经由 Passport::routes
方法注册,因此不需要手动定义它。如果请求成功,会在服务端返回的 JSON 响应中收到一个 access_token
和 refresh_token
:
$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);
请求所有作用域
使用密码授权机制时,可以通过请求 scope 参数 *
来授权应用程序支持的所有范围的令牌。如果你的请求中包含 scope 为 *
的参数,令牌实例上的 can
方法会始终返回 true
。这种作用域的授权只能分配给使用 password
授权时发出的令牌:
$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' => '*',
],
]);
自定义用户名字段
当使用密码授权时,Passport 默认使用 email
作为「用户名」。但是,你可以通过在模型上定义一个 findForPassport
方法来自定义用户名字段:
<?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;
/**
* 通过用户名找到对应的用户信息
*
* @param string $username
* @return \App\User
*/
public function findForPassport($username)
{
return $this->where('username', $username)->first();
}
}
使用方法
使用上面的 /oauth/token
接口如果成功的话,将会返回类似下面的数据:
{
access_token: "eyJ0eXAiOiJK...",
expires_in: 31622400,
refresh_token: "def50200...",
token_type: "Bearer"
}
我们可以在 Header 中 或者请求中添加所得的 access_token
来进行认证
Authorization header
Authorization: Bearer eyJ0eX....
Query string parameter
http://example.test/xxxx?token=eyJ0eX....