通过 Passport 实现 API 请求认证:移动端应用篇(密码授权令牌)


学院君注:虽然教程的名字叫移动端应用篇,但是完全适用于同一个公司不同系统间的认证,包括不同 Web 网站间认证。

虽然我们在上一篇教程中做好了 Passport 后端初始化配置,但是注册的相关路由和初始化的数据库数据并没有用到,从这一篇教程开始,我们将基于 Passport 实现一些更复杂的 API 授权认证,会逐步用到这些路由和数据库记录。

我们在上一篇教程中已经介绍了单页面应用的 API 认证,这篇教程我们将就移动端应用的 API 请求认证展开讨论,这些移动端应用包括客户端 App、H5应用(从 Web 网站分离出去的独立移动网站,基于 HTML5 开发,通常简称 H5 应用,域名与 Web 网站不同)。对于这些自有网站,我们通常也不希望进行常见的 OAuth 跳转授权,会影响用户体验,因此我们基于 Passport 提供的密码授权令牌来实现相应的 API 请求认证。

1、创建一个测试移动端应用

既然是分离的独立应用之间的认证,我们先来创建一个新的应用用来测试,名字叫做 testapp

composer create-project --prefer-dist laravel/laravel testapp

安装完成后,进入应用根目录运行 npm install 初始化前端资源,然后配置这个应用的域名为 app.test

下面我们将通过 testapp 应用来模拟移动端应用,通过主项目 blog 作为后端应用,移动端应用访问后端应用的认证 API 接口时,需要后端应用授权才能访问,在 OAuth 服务中,这个授权通过颁发一个访问令牌实现。我们使用密码授权令牌的原因是和授权码令牌相比,这个过程中没有授权确认和跳转,整个过程就像是用户提交登录表单进行认证一样。下面我们就来简单演示下这个授权过程。

2、在后端应用中注册移动端应用

要让移动端应用和后端应用之间进行授权认证,需要在后端应用 blog 中注册对应的移动端应用 testapp,我们在 blog 项目根目录下运行这个命令来注册 testapp

php artisan passport:client --password

我们将应用的名称设置为 testapp 就好了,其它都会自动生成,执行完毕后在 oauth_clients 数据表中修改对应数据库记录的 redirect 字段值为 http://app.test/auth/callback

这样,我们就完成了移动端应用在后端应用中的注册。

注:对于大型项目来说,可以通过后台注册中心申请审核的方式完成新应用的注册,方便统一管理。这里我们只是通过 Artisan 命令快速演示。

3、配置移动端应用

回到 testapp 应用,在项目根目录下的 .env 文件中新增两个配置项,将刚刚在后端应用中注册的 testapp 应用配置信息填写到这里:

CLIENT_ID=7
CLIENT_SECRET=2JPrCvRyoJ14f0OqCe6nnQZNDfPLNNPY7TcfDnco

然后在 config/services.php 中新增如下配置项:

'blog' => [
    'appid' => env('CLIENT_ID'),
    'secret' => env('CLIENT_SECRET'),
    'callback' => 'http://app.test/auth/callback'
]

4、在移动端应用填写登录表单进行登录

接下来,在 testapp 应用中,我们将借助 Laravel 自带的认证脚手架快速实现认证路由和视图,以完成登录表单和请求提交:

php artisan make:auth

然后在 Auth/LoginController 控制器中重写 login 方法,将默认到数据库检查用户登录凭证的逻辑改为将请求发送到后端应用获取授权令牌:

// 在控制器顶部引入如下命名空间
use GuzzleHttp\Client;
use Illuminate\Http\Request;

// 重写 AuthenticatesUsers 中的 login 方法
public function login(Request $request)
{
    $request->validate([
        'email' => 'required|string',
        'password' => 'required|string',
    ]);

    $http = new Client();
    // 发送相关字段到后端应用获取授权令牌
    $response = $http->post('http://blog.test/oauth/token', [
        'form_params' => [
            'grant_type' => 'password',
            'client_id' => config('services.blog.appid'),
            'client_secret' => config('services.blog.secret'),
            'username' => $request->input('email'),  // 这里传递的是邮箱
            'password' => $request->input('password'), // 传递密码信息
            'scope' => '*'
        ],
    ]);

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

5、测试后端应用授权移动端认证

这一次,我们不需要在 testapp 中创建用户表数据库,直接在浏览器访问移动端应用登录表单 http://app.test/login,输入我们在后端应用 blog 中注册过的用户信息,就好像我们在 testapp 中注册过它们一样:

点击登录,会将用户登录凭证和应用配置数据传递到后端应用的 oauth/token 路由,通过后端应用中 Passport 底层的密码授权类进行校验,校验成功后就会返回令牌信息给移动端应用:

返回结果中包含四个字段,access_token 是授权令牌,token_type 表示认证类型是 Bearer,我们可以将这个 access_token 值设置到 Bearer Authentication 请求头去请求需要认证的后端 API 接口。refresh_token 在令牌过期后刷新令牌时使用,最后 expires_in 表示令牌有效期(单位是秒,即有效期一年)。

接下来,以上篇教程中提到的后端认证 API 接口 http://blog.test/api/user 为例在 Postman 中进行演示,我们选择请求 Authorization 的类型为 Bearer Token,然后将 access_token 值拷贝到下图的 Token 字段中,发起请求就可以获取到认证接口返回数据了:

6、通过 JavaScript 消费 API

以上只是模拟测试,如果是在真实的 App 或者 H5 应用环境中完成上述操作,登录表单和获取授权令牌的逻辑和上面类似,只不过需要将对应的 PHP 代码转化为 JavaScript 代码或客户端语言代码。获取到请求令牌(access_token)值后将其存储到本地 Cookie 或者页面头部的 meta 标签里,后续请求后端认证 API 接口时带上 Bearer 认证请求头就好了,以 H5 为例,如果我们将令牌值存储在 meta 标签中,可以这么设置全局请求头:

let api_token = document.head.querySelector('meta[name="api-token"]');

if (api_token) {
    window.axios.defaults.headers.common['Authorization'] = 'Bearer ' + api_token.content;
}

这样,我们就可以通过 axios 库发起对后端认证 API 的请求了。

7、令牌的有效期

设置有效期

如果你做过微信、微博之类开发平台接口接入一定对令牌有效期有所了解,Passport 生成的授权令牌默认有效期是一年,但是为了提升系统安全性,也可以自定义配置其有效期。我们可以在 AuthServiceProviderboot 方法中通过 Passport 门面上的 tokensExpireInrefreshTokensExpireIn 方法来设置令牌有效期:

Passport::tokensExpireIn(now()->addDays(7));
Passport::refreshTokensExpireIn(now()->addDays(7)); 

以上两个方法等效,使用任意一个都可以,都是设置授权令牌有效期为 7 天。过期之后,需要刷新令牌。

刷新令牌

当授权访问令牌过期后,我们可以通过在 oauth/token 路由请求中指定操作类型为 refresh_token 来刷新令牌:

$http = new GuzzleHttp\Client;

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

return response($response->getBody());

刷新令牌后,会生成新的令牌并返回,同时将老的令牌撤销。


Vote Vote Cancel Collect Collect Cancel

<< 上一篇: 通过 Passport 实现 API 请求认证:单页面应用篇

>> 下一篇: 通过 Passport 实现 API 请求认证:第三方应用篇(授权码获取令牌)