适用于单页面应用的轻量级用户认证解决方案 Laravel Airlock 功能速览


声明:因商标名称纠纷,Airlock 已经更名为 Sanctum。

昨天我在知识星球上提到了 Laravel Airlock 这个即将发布的官方扩展包,那个时间点 Taylor Otwell(Laravel 框架作者)也是刚刚将扩展包源码提 Github,以便让开发者参与早期测试和体验:

Laravel Airlock 即将发布

今天,学院君就带大家来体验下这个扩展包的使用。

为什么提供这个扩展包

Laravel Airlock 的官方定位是适用于单页面应用(SPA)和简单 API 接口的轻量级用户认证解决方案,所以它的定位仍然是 API 认证解决方案。

有人说不是已经有 Laravel Passport 这个 API 认证解决方案了吗?但是用过的同学应该都知道 Passport 这套东西还是比较复杂,对于单页面应用或者简单 API 接口有点杀机焉用牛刀的感觉(学院君曾经花过多达七篇教程来系统介绍 Passport 的功能)。

又有人会说,Laravel 也提供了基于 token 驱动的 API 认证了啊,Oh My God,那就是个玩具而已,只能在本地开发或者演示项目中用用,生产环境还是算了,不够安全。

基于此背景,Airlock 应运而生。

Laravel Airlock 使用入门

接下来,学院君给大家简单介绍下如何在 Laravel 项目中集成并使用 Airlock 进行 API 认证。

你也可以在 GitHub 上查看项目源码和英文文档:https://github.com/laravel/airlock

安装配置

Laravel Airlock 仅支持 Laravel 6 及以上版本,所以我们需要在一个基于 Laravel 6 驱动的应用中通过 Composer 安装这个扩展包:

composer require laravel/airlock

安装完成后,通过 vendor:publish 命令发布扩展包的配置和迁移文件:

php artisan vendor:publish --provider="Laravel\Airlock\AirlockServiceProvider"

最后,运行数据库迁移命令生成扩展包需要的数据表:

php artisan migrate

该命令会新创建一个 personal_access_tokens 表用于存放用户 API 认证信息:

personal_access_tokens数据表

接下来,如果你是为单页面应用提供认证的话,可以在配置文件 config/airlock.php 中通过 stateful 配置请求来源域名,以便系统确认哪些域名需要维护认证状态(通过在这些域名下设置认证相关 Cookie 实现,通常,你可以将系统所有域名配置进来):

'stateful' => [
    'laravel6.test',
    'h5.laravel6.test',
],

另外在 airlock.php 配置文件中还有一个 expiration 配置项,用于配置令牌过期时间(单位为分钟),默认为 null 表示永远不过期。

设置中间件和认证守卫

接下来,我们要在 app/Http/Kernel.phpapi 中间件分组中应用 Airlock 中间件:

use Laravel\Airlock\Http\Middleware\EnsureFrontendRequestsAreStateful;

...

protected $middlewareGroups = [
    ...
    'api' => [
        EnsureFrontendRequestsAreStateful::class,
        'throttle:60,1',
        'bindings',
    ],
];

...

该中间件会判断请求是否来自前面 stateful 配置项中的前端域名,如果是的话会进行 CSRF 验证确保请求安全、启动 Session、设置 Cookie、并在请求中标识 airlock

EnsureFrontendRequestsAreStateful源码

接下来,我们就可以使用 auth:airlock 中间件指定认证守卫为 Airlock 了,在 routes/api.php 中,我们可以这样定义基于 Airlock 认证的 API 路由:

Route::middleware('auth:airlock')->get('/user', function (Request $request) {
    return $request->user();
});

如果这个 API 路由还对第三方应用提供了基于 Passport 的 OAuth2 认证,你可以通过指定多个认证守卫让它们同时生效:

Route::middleware('auth:airlock,passport')->get('/user', function (Request $request) {
    return $request->user();
});

Airlock 认证底层实现原理

具体的 Airlock 认证守卫实现逻辑定义在 vendor/laravel/airlock/src/Guard.php 中,核心逻辑位于 __invoke 魔术方法:

Airlock Guard 源码

如果用户已经通过 auth:web 认证,则判断用户模型是否支持 API 令牌(判断依据是是否使用了 HasApiTokens Trait),如果支持则在用户实例上设置令牌并返回,否则直接返回用户实例。

如果用户没有通过 auth:web 认证,则在用户模型支持 API 令牌,并且在 Bearer Authorization 请求头中存在令牌信息的情况下,根据该令牌信息获取用户令牌模型实例(对应 personal_access_tokens 表记录),如果令牌不存在或者已过期则认证失败;否则返回令牌模型关联的包含令牌字段的用户实例。

其他情况下,认证失败。

基于以上源码分析,要让 Airlock 认证生效,还要在用户模型中使用 HasApiTokens Trait:

use Laravel\Airlock\HasApiTokens;

class User extends Authenticatable
{
    use Notifiable,HasApiTokens;
    
    ...

基于 Airlock 进行 API 认证

要认证单页面应用,应用的登录页需要先请求 /airlock/csrf-cookie 路由来初始化 CSRF 保护,我们编写一段简单的客户端测试代码示例如下:

window.axios.get('/airlock/csrf-cookie').then(response => {
    window.axios.post('/airlock/token', {
        'email': 'test@xueyuanjun.com',
        'password': 'test123456'
    }).then(response => {
        console.log('认证成功');
        let authToken = response.data;
        window.axios.defaults.headers.common['Authorization'] = `Bearer ${authToken}`;
        window.axios.get('/api/user').then(response => {
            // 打印用户信息
            console.log(response.data)
        });
    }).catch(error => {
        // 登录失败,打印错误信息
        console.log(error);
    });
});

CSRF 保护初始化之后,可以发送 POST 请求到 /airlock/token 路由通过用户登录凭证(邮箱/密码)获取认证令牌,然后后续 API 接口请求就可以带上这个认证令牌通过 Airlock 守卫实现自动认证。

/airlock/csrf-cookie 路由已经由 Airlock 扩展包提供了,这里我们还需要在 routes/web.php 中新增 /airlock/token 路由定义:

use App\User;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\ValidationException;

Route::post('/airlock/token', function (Request $request) {
    $request->validate([
        'email' => 'required|email',
        'password' => 'required'
    ]);

    $user = User::where('email', $request->email)->first();

    if (! $user || ! Hash::check($request->password, $user->password)) {
        throw ValidationException::withMessages([
            'email' => ['The provided credentials are incorrect.'],
        ]);
    }

    return $user->createToken('auth-token')->plainTextToken;
});

这里的主要逻辑是验证用户注册邮箱和密码是否匹配,如果匹配的话则通过 HasApiTokens 提供的 createToken 方法颁发令牌。

接下来,我们编译前端资源,并访问引用上述 Axios 代码的页面,F12 打开控制台,可以在网络请求中看到上述请求都已经成功:

客户端网络请求

在 Console 中可以看到打印的日志信息,表明 API 认证接口请求成功:

客户端认证日志

小结

本篇教程只是抛砖引玉,Airlock 还支持撤销令牌、自定义用户模型及令牌模型,更多关于 Airlock 的使用方法请关注 laravel/airlock 这个项目。

最后简单总结下,Airlock 是一个轻量级的 API 认证解决方案,尤其适用于单页面应用,与 Passport 相比,更加精简,没有那么臃肿,与之前的 Token 方案相比,虽然也是基于 API 令牌,但更加安全,支持 CSRF 保护,支持令牌作用域和权限,支持令牌撤销。

声明:Laravel Airlock 目前还处于开发测试阶段,不建议在生产环境使用。


点赞 取消点赞 收藏 取消收藏

<< 上一篇: Laravel 8 新特性和功能优化速览

>> 下一篇: Laravel 7 将支持直接在路由定义中自定义隐式路由模型绑定