Laravel 框架扩展 Auth 认证,实现自定义 driver,guard

原文链接:https://fordawn.com/article/36


最近研究 laravel 自带的认证,发现使用token认证方式的话,默认传值字段和数据库中字段名都是api_token,这让我很不爽,laravel 这样的框架一定有什么自定义的途径,网上查询无果,那就自己看看源码吧。

我们知道,认证其实就是调用Auth::guard()来执行的,那我们不妨从这里开始看起。

自定义dirver配置项

我们知道AuthIlluminate/Auth/AuthManager.php的alias,那我们打开这个文件,找到guard后,发现判断用实际哪个guard的代码在方法resolve中,其中有一段代码:

if (isset($this->customCreators[$config['driver']])) {
    return $this->callCustomCreator($name, $config);
}

发现这个类中有一个数组customCreators,这说明,我们是可以自定义driver的。也就是说,虽然config/auth.phpguards中说driver只支持sessiontoken,但实际上是可以自己扩展的。

那怎么扩展呢,顺藤摸瓜,发现class AuthManager中有一个方法extend

public function extend($driver, Closure $callback)
{
    $this->customCreators[$driver] = $callback;

    return $this;
}

这个方法就是给Auth添加自定义driver用的,那我们直接找个provider扩展一下Auth,把自己的driver添加进去就好。

我们可以先看一下默认的token driver是怎么写的:

public function createTokenDriver($name, $config)
{
    $guard = new TokenGuard(
        $this->createUserProvider($config['provider'] ?? null),
        $this->app['request']
    );

    $this->app->refresh('request', $guard, 'setRequest');

    return $guard;
}

就这么几行代码,在5.6以后TokenGuard的构造函数是有4个参数的,原型如下

public function __construct(UserProvider $provider, Request $request, $inputKey = 'api_token', $storageKey = 'api_token');

也就是说,我们把后两个参数传进去就可以了。那就开始搞起,例如在AppServiceProviderboot方法中添加:

Auth::extend('web-token', function ($app, $name, $config) {
    $guard = new WebTokenGuard(
        $this->createUserProvider($config['provider'] ?? null),
        $app['request'],
        'token',
        'token'
    );

    $app->refresh('request', $guard, 'setRequest');

    return $guard;
});

这样我们就有了一个新的driver——web-token,config中我们就可以写:

'guards' => [
    'web-api' => [
        'driver' => 'web-token',
        'provider' => 'tokens',
    ],
],

这里发现我的provider也不是users,是因为我新建了一张表专门存token用,配置起来很方便,在同文件的providers中添加如下配置即可:

'tokens' => [
    'driver' => 'eloquent',
    'model' => App\Models\Token::class,
],

但是这样一来,就产生了新的问题:我们在使用laravel的时候,总会使用类似Auth::id()Auth::user()这样的方法,因为确实很方便,而我们的认证现在虽然没有问题了,但是因为跟user不是同表,这些方法现在不能用了,那怎么办呢?我们首先来解决第一个问题:

实现 Auth:id()

我们可以看一下框架带的User Model,发现其继承自Illuminate\Foundation\Auth\User,顺藤摸瓜,我们发现他实现了三个接口:

use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;

这三个接口各有作用,比方说第一个AuthenticatableContract,就是我们目前要实现的,因为其中有一个方法public function getAuthIdentifier();,这个方法就是执行Auth::id()时候会调用的。

第二个接口是授权时候用的,第三个是重置密码时候用的,这两个我们暂时不需要,那就等以后用到时候再说。

我们现在让Token Model实现第一个接口,方法的实现根据各自的情况有所不同,例如我的token表中,用户的id列名为user_id,那我的方法就是这样子的:

public function getAuthIdentifierName()
{
    return 'user_id';
}

而因为我实现了实体关系:

public function user()
{
    return $this->belongsTo('App\User');
}

其他的一些方法可以这样写:

public function getAuthPassword()
{
    return $this->user->password;
}

这样大体上第一个问题我们就解决了,接下来解决第二个问题。

实现 Auth:user()

首先,我们来找一下这个user()方法在哪里,看了AuthManager以后发现,这个user()原来是调用的Guard中的方法,那怎么办呢?我们扩展一下TokenGuard吧。

新建一个php类继承于TokenGuard,并重写user()方法:

class WebTokenGuard extends TokenGuard
{
    public function user()
    {
        $token = parent::user();

        if (!$token) {
            return null;
        }

        return $token->user;
    }
}

return null并不会产生问题,这样会使Guard的check()方法返回false,抛出AuthenticationException异常,这与正常的逻辑是一致的。

这样我们就解决了Auth::user()的问题,扩展类还可以解决一些其他的问题,例如在5.4版本中,$inputKey$storageKey是直接写在构造函数中的,而不是参数穿进去的,扩展类也可以解决这个问题。

优化

因为使用用了新的Guard类,我们在provider中的代码也需要做一些修改,而同时,我们把字段名也写到config中,便于以后修改,优化后的代码如下:

# auth.config
'web-api' => [
    'driver' => 'web-token',
    'input_key' => 'token',
    'storage_key' => 'token',
    'provider' => 'tokens',
],

# AppServiceProvider 的 boot
Auth::extend('web-token', function ($app, $name, $config) {
    $guard = new WebTokenGuard(
        $this->createUserProvider($config['provider'] ?? null),
        $app['request'],
        $config['input_key'],
        $config['storage_key']
    );

    $app->refresh('request', $guard, 'setRequest');

    return $guard;
});

这样基本就趋近于最终版本了。

laravel 确实是一个庞大的框架,这次只涉及到了认证的问题,而未谈及授权,例如Auth::can()这样的方法,现在也是不能使用的。

授权也是一个大头的问题,不同的需求采用的方式不尽相同,laravel也自带一套授权的机制,希望本篇文章可以达到一个抛砖引玉的作用,给小伙伴们一些启发。

本作品采用《CC 协议》,转载必须注明作者和本文链接
本帖由系统于 5年前 自动加精
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 5

不足之处,还请指正

5年前 评论

Auth::createUserProvider()

4年前 评论

都成功了???

3年前 评论

不错,我们公司有好几个月业务,业务之间要用相同的user,驱动就是这样写的。

2年前 评论

如果是从其他接口里 根据ID获取用户信息,相当于是不从 eloquent 和 database 里取内容, 这个 guard 要怎么写呢?

1年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!