前几天用 Laravel 做了一套API接口,在认证Token时,Laravel默认会在user表的api_token字段验证,这样请求量大了数据库压力会很大。所以需要吧Token存在Redis进行验证,那么用Redis是如何验证Token呢?
【一】查找
在auth.php文件中我们可以看到 providers 的驱动仅支持 eloquent 或者 database,我们的重点就是让 providers 支持 redis。所以,我们需要找到providers 的驱动控制方法。经过一番查找最终在 Illuminate\Auth\CreatesUserProviders 中找到一个名为 createUserProvider 的方法。
... public function createUserProvider($provider = null) { if (is_null($config = $this->getProviderConfiguration($provider))) { return; } if (isset($this->customProviderCreators[$driver = ($config['driver'] ?? null)])) { return call_user_func( $this->customProviderCreators[$driver], $this->app, $config ); } switch ($driver) { case 'database': return $this->createDatabaseProvider($config); case 'eloquent': return $this->createEloquentProvider($config); default: throw new InvalidArgumentException( "Authentication user provider [{$driver}] is not defined." ); } } ...
首先获取 auth.providers 里的配置,如果存在 $this->customProviderCreators 自定义 Provider 创建方法,调用该方法创建 Provider;否则就根据传入的 $provider 参数创建内建的 Provider。
这里的 $this->customProviderCreators 就是我们创建自定义 Provider 的关键了。
查看代码,发现在 Illuminate\Auth\AuthManager 里的 provider() 方法对这个数组进行了赋值:
... public function provider($name, Closure $callback) { $this->customProviderCreators[$name] = $callback; return $this; } ...
传入两个参数: $name , Provider 的标识; $callback , Provider 创建闭包。
就是通过调用这个方法,传入自定义 Provider 创建方法,就会可以把自定义的 Provider 放入使用的 guard 中,以达到我们的目的。
【二】定位
首先是使用 Redis 的 Provider。
供 Auth 使用的 Provider 必须实现 Illuminate\Contracts\Auth\UserProvider 接口:
这个接口给出了几个方法:
· retrieveById() : 通过 id 获取用户
· retrieveByToken() : 通过 token 获取用户。注意,这里的 token 不是我们要用的 api_token ,是 remember_token
· updateRememberToken() : 更新 remember_token
· retrieveByCredentials() : 通过凭证获取用户,比如用户名、密码,或者我们这里要用到的 api_token
· validateCredentials() : 验证凭证,比如验证用户密码
我们的需求就是 api 传入 api_token ,再通过存在 Redis 里的 api_token 来获取用户,并交给 guard 使用。
上面我们看到了,在 Illuminate\Auth\TokenGuard 里:
... public function user() { // If we've already retrieved the user for the current request we can just // return it back immediately. We do not want to fetch the user data on // every call to this method because that would be tremendously slow. if (! is_null($this->user)) { return $this->user; } $user = null; $token = $this->getTokenForRequest(); if (! empty($token)) { $user = $this->provider->retrieveByCredentials([ $this->storageKey => $this->hash ? hash('sha256', $token) : $token, ]); } return $this->user = $user; } ...
是调用 $this->provider->retrieveByCredentials() 根据凭证获取用户的,我们用 api_token 换用户的操作在这个方法里实现。
根据需求,最方便的方法是复用 Illuminate\Auth\EloquentUserProvider 类,并重载 retrieveByCredentials() 方法,就可以实现我们想要的功能了。
【三】新建
Exceptions目录下新建文件RedisUserProvider.php
<?php namespace App\Exceptions; use Illuminate\Support\Facades\Redis; use Illuminate\Auth\EloquentUserProvider; class RedisUserProvider extends EloquentUserProvider { /** * Retrieve a user by the given credentials. * * @param array $credentials * @return \Illuminate\Contracts\Auth\Authenticatable|null */ public function retrieveByCredentials(array $credentials) { if (!isset($credentials['api_token'])) return ; $userId = Redis::GET('user_tokens:' . $credentials['api_token']); if ($userId) { $user = Redis::GET('user_infos:' . $userId); //从redis中读取 return json_decode($user); } // if ($userId) return $this->retrieveById($userId); //从数据库读取 } }
好了,剩下要做的就是在 TokenGuard 里使用这个 Provider 了。
前面我们提到在 Illuminate\Auth\AuthManager::provider() 里设置 customProviderCreators 可以达成这个目的。
找到位置就好办,我们在 App\Providers\AppServiceProvider 注册这个方法:
... use App\Exceptions\RedisUserProvider; ... public function register() { $this->app->make('auth')->provider('redis', function ($app, $config) { return new RedisUserProvider($app['hash'], $config['model']); }); } ...
现在,我们在控制器中就可以接收到redis返回的用户信息了
... use Illuminate\Http\Request; ... public function __construct(Request $request) { $this->request = $request; } public function info() { $user = $this->request->user(); dump($user); }
【四】结合
通过VerifyCsrfToken来结合Token验证
... use Illuminate\Support\Facades\Auth; ... protected $except = [ // '*', ]; public function handle($request, Closure $next) { if(!$this->inExceptArray($request)){ if (Auth::guest()) { $result = [ 'data' => (object)[], 'code' => 300, 'msg' => 'Token验证失败,请重新登录', ]; return response()->json($result); } } return $next($request); }
一切就绪,现在只需要在接口中传入正确的Authorization就可以进行身份验证了。
登录后可发表评论