使用 Dingo API 快速构建 RESTful API(九)—— API 认证实现(下)

上篇教程中,我们介绍了如何通过 HTTP 基本认证和 JWT 认证实现 Dingo API 的认证,这篇教程,学院君将会给大家介绍如何在 Dingo API 中基于 OAuth 2.0 和自定义认证驱动实现 API 认证。

OAuth 2.0 认证

Laravel Passport 一样,Dingo API 的 OAuth 2.0 认证基于第三方扩展包 league/oauth2-server 实现,该扩展包还提供了针对 Laravel 框架的适配包 lucadegasperi/oauth2-server-laravel,因此,在 Dingo API 中实现 OAuth 2 认证的话,直接安装配置后面这个扩展包即可。但是从 Laravel 5.3 开始,由于 Laravel 官方提供的 API 认证扩展包 Passport 也是基于 league/oauth2-server 实现的,所以后面这个适配包就废弃了,不仅如此,Dingo API 后续的版本也移除了 OAuth 2 驱动(Dingo\Api\Auth\Provider\OAuth2),所以,如果你是在 Dingo API 包含 OAuth2 且 Laravel 框架版本低于 5.3 的老版本中实现 OAuth 2 认证,可以使用 lucadegasperi/oauth2-server-laravel 这个适配包快速实现,否则需要在 Dingo API 中自定义 OAuth2 驱动并自行配置 league/oauth2-server 实现 OAuth 2 认证,或者,你可以直接基于 Passport 实现 Dingo 的认证,包括上篇教程提到的 JWT 认证,也可以基于 Passport 实现(Passport 使用了另一个 JWT 扩展包实现基于 JWT 的 API 认证)。

在这篇教程中,为了简化流程,我们将直接基于 Passport 实现 Dingo API 的 OAuth2 认证(5.3 之前老版本实现可以参考 Dingo 文档),关于 OAuth2 的底层原理,可以参考 Passport 文档中的介绍,这里我们将重点放到认证实现上。

安装配置 Passport

如果你还没有在项目中安装并初始化 Passport 扩展包的话,可以参考 Passport 官方文档进行安装配置。这里由于我们基于待办任务项目进行演示,在之前的教程中已经初始化过,所以可以跳过这一步骤。

最后,记得将 config/auth.php 中的 guards.api.driver 配置值调整为 passport 以便在应用中生效:

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

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

设置认证中间件

基于 Passport 认证的话,就需要修改之前设置的认证中间件了,打开 Api\TaskController.php,将定义在构造函数中的认证中间件调整为 auth:api

public function __construct()
{
    $this->middleware('auth:api');
}

这样一来,在认证的时候就会通过 Laravel 框架提供的中间件来校验了,此外,还需要修改获取认证用户的代码:

public function index(Request $request)
{
    $limit = $request->input('limit') ? : 10;
    // 获取当前认证用户实例
    $user = $request->user();
    $tasks = Task::where('user_id', $user->id)->paginate($limit);
    return $this->response->paginator($tasks, new TaskTransformer());
}

通过密码授权令牌访问认证 API

OAuth 2 提供了多种获取授权令牌的方法,比如通过授权码颁发访问令牌、密码授权令牌、隐式授权令牌、私人访问令牌等,具体实现可以参考 Passport 官方文档或者学院君写的系列教程,这里我们以密码授权令牌为例做演示。

首先,我们通过如下 Artisan 命令创建一个新的需要接入 API 认证的客户端应用:

php artisan passport:client --password

Passport Client

这样一来,我们就获取到对应的 APP ID 和 APP Secret,将其配置到 .env 中:

CLIENT_ID=7
CLIENT_SECRET=7lz6yKdRWudXgwtct6esjwEjk8DpjjFs10lMkvFh

然后我们在 routes/api.php 中定义一个用于获取授权令牌的路由:

$api->version('v3', function ($api) {
    ...
    $api->post('user/token', function () {
        app('request')->validate([
            'email' => 'required|string',
            'password' => 'required|string',
        ]);

        $http = new \GuzzleHttp\Client();
        // 发送相关字段到后端应用获取授权令牌
        $response = $http->post(route('passport.token'), [
            'form_params' => [
                'grant_type' => 'password',
                'client_id' => env('CLIENT_ID'),
                'client_secret' => env('CLIENT_SECRET'),
                'username' => app('request')->input('email'),  // 这里传递的是邮箱
                'password' => app('request')->input('password'), // 传递密码信息
                'scope' => '*'
            ],
        ]);

        return response()->json($response->getBody()->getContents());
    });
    $api->resource('tasks', \App\Http\Controllers\Api\TaskController::class);
});

当我们在 Postman 中模拟访问 user/token 时,就可以获取到对应的访问令牌了:

通过 Passport 密码授权获取访问令牌

其中 access_token 就是后续访问认证 API 时需要的令牌。还是在 Postman 中,我们将 access_token 字段值设置到类型为 Bearer 的 Authorization 字段,然后请求 tasks.index 路由,如果能返回相应的响应数据,表示认证成功:

Dingo 基于 Passport 实现 OAuth2 认证

至此,在 Dingo API 中基于 Passport 实现 OAuth2 认证就演示到这里,其它类型的 OAuth2 认证请参考学院君上篇提到的教程自行去实践。

自定义认证驱动

如果以上介绍的 HTTP 基本认证、JWT 认证以及 OAuth2 认证驱动都不能满足你的需求,你还可以选择在 Dingo 中自定义认证驱动。自定义认证驱动类需要实现 Dingo\Api\Contract\Auth\Provider 接口,如果认证成功,则返回对应的认证用户实例,否则要抛出 Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException 异常:

use Illuminate\Http\Request;
use Dingo\Api\Routing\Route;
use Dingo\Api\Contract\Auth\Provider;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;

class CustomProvider implements Provider
{
    public function authenticate(Request $request, Route $route)
    {
        // Logic to authenticate the request.

        throw new UnauthorizedHttpException('Unable to authenticate with supplied username and password.');
    }
}

如果你需要使用从 Authorization 请求头中获取到的令牌,和 Dingo 默认提供的 BasicJWT 驱动一样,可以选择继承 Dingo\Api\Auth\Provider\Authorization 基类,该基类也实现了 Dingo\Api\Contract\Auth\Provider 接口,只是新增了从 Authorization 请求头获取字段值的逻辑,我们在自定义驱动中只需要实现 getAuthorizationMethod 方法来返回 Authorization 请求头的认证类型即可:

use Illuminate\Http\Request;
use Dingo\Api\Routing\Route;
use Dingo\Api\Auth\Provider\Authorization;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;

class CustomProvider extends Authorization
{
    public function authenticate(Request $request, Route $route)
    {
        $this->validateAuthorizationHeader($request);

        // If the authorization header passed validation we can continue to authenticate.
        // If authentication then fails we must throw the UnauthorizedHttpException.
    }

    public function getAuthorizationMethod()
    {
        return 'mac';
    }
}

定义完自定义驱动类就可以在配置文件 config/api.php 中配置通过自定义驱动实现 Dingo API 的认证:

'auth' => [
    'custom' => 'CustomProvider',
],

或者在服务提供者的 boot 方法中扩展 Dingo 认证管理器驱动数组以便在应用中可以通过自定义驱动进行 API 认证:

app(\Dingo\Api\Auth\Auth::class)->extend('custom', function ($app) {
    return new CustomProvider;
});

关于 Dingo API 的认证实现学院君就介绍到这里,下一篇教程我们将开始介绍 API 接口访问频率限制的实现。

上一篇: 使用 Dingo API 快速构建 RESTful API(八)—— API 认证实现(上)

下一篇: 使用 Dingo API 快速构建 RESTful API(十)—— 路由访问频率限制