Laravel 框架扩展 Auth 认证,实现自定义 driver,guard
起
最近研究 laravel 自带的认证,发现使用token认证方式的话,默认传值字段和数据库中字段名都是api_token
,这让我很不爽,laravel 这样的框架一定有什么自定义的途径,网上查询无果,那就自己看看源码吧。
寻
我们知道,认证其实就是调用Auth::guard()
来执行的,那我们不妨从这里开始看起。
自定义dirver配置项
我们知道Auth
是Illuminate/Auth/AuthManager.php
的alias,那我们打开这个文件,找到guard
后,发现判断用实际哪个guard的代码在方法resolve
中,其中有一段代码:
if (isset($this->customCreators[$config['driver']])) {
return $this->callCustomCreator($name, $config);
}
发现这个类中有一个数组customCreators
,这说明,我们是可以自定义driver的。也就是说,虽然config/auth.php
的guards
中说driver
只支持session
和token
,但实际上是可以自己扩展的。
那怎么扩展呢,顺藤摸瓜,发现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');
也就是说,我们把后两个参数传进去就可以了。那就开始搞起,例如在AppServiceProvider
的boot
方法中添加:
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 协议》,转载必须注明作者和本文链接
推荐文章: