通过 Passport 实现 API 请求认证:令牌作用域详解


通过前面几篇教程,学院君给大家完整介绍了 API 认证的各种实现方式,你应该可以总结出一个规律:API 接口认证归根结底其实就是获取授权令牌,然后在请求中带上这个令牌对认证接口进行访问的过程,不同的实现方式其实就是获取令牌的方式不同而已,后面都是一样的。

除此之外,在认证过程中,有时候我们还需要对令牌的授权作用域进行限制,不是认证接口的所有返回数据都可以通过该令牌进行访问,或者不是所有接口都需要通过该令牌进行访问,是否能够获取对应数据或访问对应接口取决于用户的主动勾选。我们以腾讯视频登录功能为例,如果选择通过 QQ 账号进行登录,可以在右侧看到权限选择面板,默认权限是获取用户昵称、头像、性别信息,其它信息需要用户手动勾选才能获取:

下面我们以通过授权码获取令牌为例,演示下令牌作用域功能的实现。

配置后端应用

我们在后端应用 blog 中通过 Passport::tokensCan 定义 API 认证的令牌作用域。打开 AuthServiceProvider 服务提供者类,在 boot 方法中调用该方法,设置三个令牌作用域:

// 令牌作用域
Passport::tokensCan([
    'basic-user-info' => '获取用户名、邮箱信息',
    'all-user-info' => '获取用户所有信息',
    'get-post-info' => '获取文章详细信息',
]);

basic-user-info 用于限定获取用户接口指定字段信息,all-user-info 用于获取用户接口所有字段信息,get-post-info 用于限定访问文章接口。

然后打开 app/Http/Kernel.php,在 $routeMiddleware 属性中引入两个中间件:

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

scopes 用于检查传入令牌作用域是否包含所有指定中间件参数,scope 用于检查传入令牌作用域是否包含任意指定中间件参数。听起来有点懵,下面我们举个例子。

routes/api.php 中新增一个获取文章详情信息的路由,并修改路由定义如下:

Route::middleware('auth:api')->group(function () {
    Route::get('/user', function (Request $request) {
        $user = $request->user();
        if ($user->tokenCan('all-user-info')) {
            // 如果用户令牌有获取所有信息权限,返回所有用户字段
            return $user;
        }
        // 否则返回用户名和邮箱等基本信息
        return ['name' => $user->name, 'email' => $user->email];
    })->middleware('scope:basic-user-info,all-user-info');
    Route::get('/post/{id}', function (Request $request, $id) {
        return \App\Post::find($id);
    })->middleware('scopes:get-post-info');
});

上述第一个路由应用了 scope 中间件,要求传入令牌作用域包含 basic-user-infoall-user-info 任意一个即可,并且在路由闭包中根据用户具体字段获取权限进一步进行了细分;第二个路由应用了 scopes 中间件,要求传入令牌作用域必须包含 get-post-info,如果有多个的话,可以通过逗号分隔。

在第三方应用中测试

回到第三方应用 testapp,将配置信息修改回授权码令牌对应配置,然后在 /auth 路由对应控制器方法 LoginController@oauth 中,设置 scope 请求字段值:

public function oauth()
{

    $query = http_build_query([
        'client_id' => config('services.blog.appid'),
        'redirect_uri' => config('services.blog.callback'),
        'response_type' => 'code',
        'scope' => 'all-user-info get-post-info',
    ]);

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

然后,我们在浏览器中访问 http://test.app/auth 通过授权码获取令牌,跳转到后端系统授权界面时会多出一段提示信息告知该授权令牌的作用域:

注:在本例中,我们同样简化了处理流程,直接在第三方应用控制器方法中写死了作用域,你可以像教程开头腾讯视频那样让用户自行勾选,然后通过表单传递到控制器方法。

当我们点击绿色的确认授权按钮后,就可在第三方应用中获取到授权令牌,与此同时,也会在后端应用数据表 oauth_access_tokensoauth_auth_codes 生成的记录看到对应的 scopes 字段值了。

接下来,我们可以通过或到的 access_token 在 Postman 中测试对后端认证接口的访问:

获取文章接口也没有问题:

而如果我们把第三方应用 /auth 路由传递的 scope 请求字段值调整为了 basic-user-info

public function oauth()
{

    $query = http_build_query([
        'client_id' => config('services.blog.appid'),
        'redirect_uri' => config('services.blog.callback'),
        'response_type' => 'code',
        'scope' => 'basic-user-info',
    ]);

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

再次通过 http://test.app/auth 获取令牌,令牌作用域就会发生变化:

确认授权之后,会获取到新的令牌信息,然后我们拿着这个令牌到 Postman 中测试,访问用户接口,就只能取到最基本的用户字段信息了:

而如果试图访问文章详情接口,则会返回 403 响应:

结语

以上我们以授权码令牌为例对令牌作用域功能进行了演示,其它获取令牌访问认证接口、设置令牌作用域的方式,与此类似,不再赘述。很多时候,我们在 scope 字段传入 * (或留空,每种令牌认证有所不同)表示获取到的令牌具备所有认证接口访问权限,但如果对安全性要求比较高的系统,我们也可以通过令牌作用域的方式对令牌访问权限做更细分的设置,通过 Passport 内置的脚手架,可以快速实现令牌作用域功能,非常方便。


Vote Vote Cancel Collect Collect Cancel

<< 上一篇: 通过 Passport 实现 API 请求认证:隐式授权令牌

>> 下一篇: 通过 Laravel 内置脚手架快速实现邮箱验证功能