总结Laravel用户认证
用了挺久的 laravel,因为它实在是太方便了,很多东西都是开箱即用的。
现在系统越来越大了,准备开始拆分模块,首先先拆将用户认证拆出来,之前专注于业务逻辑,没有自己研究过laravel用户认证这块,这里自我总结下,也感谢各位群里大大佬的指点:
Laravel的用户认证模块:
Guard
:守卫者,主要提供认证的机制,检查用户认证情况,laravel默认提供三种GuardTokenGuard
:主要用于无状态的API认证SessionGuard
:默认的web认证方式RequestGuard
(自定义会调函数的认证方式)
我这里主要总结下SessionGuard
Provider
:数据提供者,返回用户数据,提供给Guard用于登录、登出及认证等请求,主要有两种Provider:EloquentUserProvider
通过ORM模型获取认证用户,底层其实就是{Model}::find($id)
DatabaseUserProvider
通过数据库表,底层实际是:DB::table({table})...
方式
两者本质上都是通过查询数据库方式获取获取合法的用户数据
那么问题来了,如果是基于API的web服务,服务本身没有专用的数据库时候这个时候就会显得很麻烦了,这也不符合微服务的逻辑 ,这也是我这次主要要解决的问题
好在Laravel太强大了,很多东西可以自定义的,所以我们可以继续使用SessionGuard
(因为够强大了), 通过自定义UserProvider
从接口获取用户数据,这样就可以与数据库解耦。
Laravel实现方式分析
Laravel中用户认证的模块的接口规范定义在:namespace Illuminate\Contracts\Auth
,
框架实现的认证类位于:namespace Illuminate\Auth
基础的Guard
API:Illuminate\Contracts\Auth\Guard;
所有的Guard都应直接或简介实现当前Guar的接口,而Illuminate\Auth\GuardHelpers
就是已经为我们实现好,具体实现方式可以研究下GuardHelpers
,接口主要定义了如下方法,:
//检查用户属否登录
public function check();
//游客检测
public function guest();
//获取当前认证的登录用户
public function user();
//获取当前认证的用户主键(id)
public function id();
//根据提供的凭证来验证登录
public function validate(array $credentials = []);
//设置用户,登录成功、check()认证成后都将user保存至当前请求生命周期对象,提高效率
public function setUser(Authenticatable $user);
Statefuluard
继承自Guard
,在其基础上增加了额外接口,如login
、loginUsingId
等开箱即用的方法,SessionGuard
中实现:
// 尝试登录的凭证是否合法
public function attempt(array $credentials = [], $remember = false);
//登录一次成功后不做任何处理
public function once(array $credentials = []);
// 登录用户,成功后记录登录数据
public function login(Authenticatable $user, $remember = false);
// 使用用户ID登录
public function loginUsingId($id, $remember = false);
// 使用用户ID登录,同once
public function onceUsingId($id);
// 通过remember token 自动登录
public function viaRemember();
// 登出
public function logout();
接下去翻一下SessionGuard
的代码,个人觉得它已经很强大了,对于web模块应用来说大部分场景都已经足够用了
class SessionGuard implements StatefulGuard, SupportsBasicAuth
{
//GuardHelpers 实现接口
use GuardHelpers, Macroable;
...
public function __construct($name,
UserProvider $provider,
Session $session,
Request $request = null)
{
$this->name = $name;
$this->session = $session;
$this->request = $request;
$this->provider = $provider;
}
...
public function login(AuthenticatableContract $user, $remember = false)
{
.....
$this->updateSession($user->getAuthIdentifier());
......
$this->setUser($user);
}
...
}
可以看到要使用其登录的话必须要传入一个实现了的\Illuminate\Contracts\Auth\Authenticatable
接口的用户对象,其实主要设计用于自定义及获取主键,要实现的模型接口如下:
// 唯一标识,数据库的话对应的默认是主键
public function getAuthIdentifierName();
// 获取唯一标识的值
public function getAuthIdentifier();
// 获取认证的密码
public function getAuthPassword();
// 获取remember token
public function getRememberToken();
// 设置 remember token
public function setRememberToken($value);
// 获取 remember token 对应的字段名,比如默认的 'remember_token'
public function getRememberTokenName();
所以我们应该在模型中定义一个用于设置及获取认证的模型实现上述接口,别慌Laravel安装完也为我们准备了对应的模型,这里是我将模型移动到Models下
<?php
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable
{
...
}
追踪Illuminate\Foundation\Auth\User
<?php
namespace Illuminate\Foundation\Auth;
use Illuminate\Auth\Authenticatable;
...
class User extends Model implements
AuthenticatableContract,
AuthorizableContract,
CanResetPasswordContract
{
use Authenticatable, Authorizable, CanResetPassword, MustVerifyEmail;
}
可以发现在Illuminate\Auth\Authenticatable
中实现了上述唯一标识相关的方法,至此已完成整个Auth::login
流程
接下来再我们请求须要认证的路由时候,例如:
Route::group(['middleware' => ['auth']], function () {
Route::get("/", "HomeController@home");
});
当前路由须要经过中间件的认证,而中间件的定义namespace App\Http\Kernel
...
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
];
...
可以看到我们在route中定义的auth中间件\App\Http\Middleware\Authenticate::class
,实际上为:Illuminate\Auth\Middleware\Authenticate
,查看代码:
......
public function handle($request, Closure $next, ...$guards)
{
$this->authenticate($request, $guards);
return $next($request);
}
......
protected function authenticate($request, array $guards)
{
if (empty($guards)) {
$guards = [null];
}
foreach ($guards as $guard) {
if ($this->auth->guard($guard)->check()) {
return $this->auth->shouldUse($guard);
}
}
$this->unauthenticated($request, $guards);
}
看到整个认证的核心:获得须要认证的guard并调用check
方法,check方法体位于GuardHelpers
, 也就是我们如果须要自定义Guard
的须实现Illuminate\Contracts\Auth\Guard
中定义的接口
check中的代码很简单:return ! is_null($this->user());
, 主要的逻辑还是根据官方默认或自定义的Guard中实现的user()
逻辑
先贴上Illuminate\Auth\SessionGuard::user()
核心代码
public function user()
{
...
//获取唯一标识
$id = $this->session->get($this->getName());
...
//根据标识读取用户数据
if (! is_null($id) && $this->user = $this->provider->retrieveById($id)) {
$this->fireAuthenticatedEvent($this->user);
}
......
//从remember me中读取自动登录
if (is_null($this->user) && ! is_null($recaller = $this->recaller())) {
$this->user = $this->userFromRecaller($recaller);
if ($this->user) {
$this->updateSession($this->user->getAuthIdentifier());
$this->fireLoginEvent($this->user, true);
}
}
...
}
从$this->user = $this->provider->retrieveById($id))
,可以看出,这时候用到了从provider中获取认证用户数据
laravel默认的Provider
为Illuminate\Auth\EloquentUserProvider
,它实现Illuminate\Contracts\Auth\UserProvider
中申明的接口
namespace Illuminate\Contracts\Auth;
interface UserProvider
{
//根据唯一标识符获取用户
public function retrieveById($identifier);
//根据token读取用户唯一标识符,无状态API适用
public function retrieveByToken($identifier, $token);
//基于认证模型更新token
public function updateRememberToken(Authenticatable $user, $token);
//基于认证模型获取用户
public function retrieveByCredentials(array $credentials);
//根据用户的给定的凭证认证用户
public function validateCredentials(Authenticatable $user, array $credentials);
}
有了以上接口就可以自定义Provider来处理不通的逻辑,义EloquentUserProvider::retrieveById()
为例子
public function retrieveById($identifier)
{
$model = $this->createModel();
return $this->newModelQuery($model)
->where($model->getAuthIdentifierName(), $identifier)
->first();
}
很简单就是获取唯一标识字段名以及值从数据库中获取一条数据,这就是我们平常经常用的Model::query()->where('id', {$id})->first()
返回ORM对象->SessionGuard::user()->check()方法至此认证完成,那么我们可以开始配置使用它了。
打开config/auth.php
return [
'defaults' => [
'guard' => 'web',
' passwords' => 'users',
],
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'session',
'provider' => 'token',
'hash' => false,
],
],
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\User::class,
],
'token' => [
'driver' => 'token',
'model' => App\Models\User::class,
]
];
default
中默认使用了web
guard,而提供了web
、api
两种方式guard,及users
、token
两种方式provider,我这里这样就可以根据自己的需求来定制Guard级Provider从而实现定制Auth认证方式。
因为SessionGuard
,功能实在是太强大了所以,我这里只是自定义了UserProvider
并实现了对应的接口,将从用户中心account.my-domain.com
获取认证用户数据,从而将应用于数据库解耦合。
大家可以根据自己的需求从而定制Guard及Provider。
代码实现
其实都说的差不多了,也就是在app/Auth/
下建立UserProvider
实现接口写自己的逻辑。
Thanks
闲话一句:本人比较擅长写api文档,清单式的流程设计,教程文档比较少些,写的不足的地方请多包涵,多指教谢谢。
也感谢@JeffreyBool这篇文章给予启发:基于 Laravel Auth 实现自定义接口 API 用户认证详解
本作品采用《CC 协议》,转载必须注明作者和本文链接
文档的内容实现一遍基本就清晰了:
用户认证《Laravel 8 中文文档》
用户认证里有两个概念:
Guard
负责认证逻辑,Provider
负责提供用户信息。所以
如果觉得官方
Guard
和第三方Guard
不适用,自己写一个Guard
,Guard
需要实现Illuminate\Contracts\Auth\Guard
,只要实现check
方法就能完成身份校验。如果觉得每次都差用户信息没必要,check里就不要去获取用户信息好了。如果觉得现有的
Guard
还不错,只是每次都查数据库太影响性能,自己写一个Provider
,Illuminate\Contracts\Auth\UserProvider
,自定义获取用户信息的方式,比如从缓存取,没几行代码。Auth::user()
只要自定义的Guard
实现了user
方法就可以用了,有user
信息直接返回,没有去proivder
取一下,也没几行代码