Laravel5.5源码详解 -- Auth中间件

Laravel5.5源码详解 – Auth中间件

为了体现全貌,前面的代码部分没有做太多删减,重点关注特别加入的注释部分。原文的注释删除,以减少阅读篇幅。本文重点在后面的流程讲解,这些比较详细。

如果光看官方的文档,碰到问题的时候往往还是不知所云。所以,熟练的运用,应该建立在对源码的深刻了解的基础上。而其流程是了解源码的第一步。了解这些,开发时才能游刃有余。

在App\Http\Kernel中注册的

<?php
namespace App\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
{
    // 全局Http中间件
    protected $middleware = [
        \Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
        \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
        \App\Http\Middleware\TrimStrings::class,
        \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
        \App\Http\Middleware\TrustProxies::class,
    ];

    // 路由中间件
    protected $middlewareGroups = [
        'web' => [
            \App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,
            // \Illuminate\Session\Middleware\AuthenticateSession::class,
            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
            \App\Http\Middleware\VerifyCsrfToken::class,
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],

        'api' => [
            'throttle:60,1',
            'bindings',
        ],
    ];

   // 可能需要分组或单独使用的中间件
    protected $routeMiddleware = [
        'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
        'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
        'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
        'can' => \Illuminate\Auth\Middleware\Authorize::class,
        'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
        'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
    ];
}

可见,Auth中间件属于可能需要分组或单独使用的中间件,当然你也可以根据需要调整。

'auth' => \Illuminate\Auth\Middleware\Authenticate::class

关于中间件的运行机理,请参考另文,

Laravel5.5源码详解 – 中间件MiddleWare分析

打开\Illuminate\Auth\Middleware\Authenticate,

<?php
namespace Illuminate\Auth\Middleware;
use Closure;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Contracts\Auth\Factory as Auth;

class Authenticate
{
    protected $auth;
    public function __construct(Auth $auth)
    {
        // 注入Illuminate\Auth\AuthManager工厂类
        $this->auth = $auth;
    }

    public function handle($request, Closure $next, ...$guards)
    {
        $this->authenticate($guards);

        return $next($request);
    }

    protected function authenticate(array $guards)
    {
        // 如果没有指定guard 则使用默认的设置
        if (empty($guards)) {

            // 使用Illuminate\Auth\AuthManager\__call()获取SessionGuard 实例,
            // 并调用其(实际是GuardHelpers的) authenticate() 方法进行认证
            return $this->auth->authenticate();

        }

        // 如果有指定guard,则使用其中第一个能成功认证的
        foreach ($guards as $guard) {
            if ($this->auth->guard($guard)->check()) {

                // 获得本次请求guard,(后续请求也都会通过这个guard)
                return $this->auth->shouldUse($guard);
            }
        }

        throw new AuthenticationException('Unauthenticated.', $guards);
    }
}

注释中说明,$this->auth在构造时传入的是Illuminate\Contracts\Auth\Factory接口,其实现类在Illuminate\Auth\AuthManager,接下来,我们看一下这个类,特别注意resolve函数中的注释,

<?php
namespace Illuminate\Auth;
use Closure;
use InvalidArgumentException;
use Illuminate\Contracts\Auth\Factory as FactoryContract;

class AuthManager implements FactoryContract
{
    use CreatesUserProviders;
    protected $app;
    protected $customCreators = [];
    protected $guards = [];
    protected $userResolver;

    public function __construct($app)
    {
        $this->app = $app;
        $this->userResolver = function ($guard = null) {
            return $this->guard($guard)->user();
        };
    }

    public function guard($name = null)
    {
        // getDefaultDriver() 返回来的就是 "web"
        $name = $name ?: $this->getDefaultDriver();   
        return $this->guards[$name] ?? $this->guards[$name] = $this->resolve($name);
    }

    protected function resolve($name)
    {
        // array:2 ["driver" => "session", "provider" => "users"]
        // array:2 ["driver" => "token","provider" => "users"]
        $config = $this->getConfig($name);

        if (is_null($config)) {
            throw new InvalidArgumentException("Auth guard [{$name}] is not defined.");
        }

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

        // "web" => "createSessionDriver"
        // "api" => "createTokenDriver"
        $driverMethod = 'create'.ucfirst($config['driver']).'Driver';

        if (method_exists($this, $driverMethod)) {

            // SessionGuard("web", ["driver" => "session", "provider" => "users"])
            // TokenGuard("api", ["driver" => "token","provider" => "users"])
            return $this->{$driverMethod}($name, $config);
        }

        throw new InvalidArgumentException("Auth guard driver [{$name}] is not defined.");
    }

    protected function callCustomCreator($name, array $config)
    {
        return $this->customCreators[$config['driver']]($this->app, $name, $config);
    }

    public function createSessionDriver($name, $config)
    {
        $provider = $this->createUserProvider($config['provider'] ?? null);

        // 在这里创建SessionGuard,
        $guard = new SessionGuard($name, $provider, $this->app['session.store']);

        if (method_exists($guard, 'setCookieJar')) {
            $guard->setCookieJar($this->app['cookie']);
        }

        // 在这里调用SessionGuard的setDispatcher()函数,关联了Dispatcher,后面再述。
        if (method_exists($guard, 'setDispatcher')) {
            $guard->setDispatcher($this->app['events']);
        }

        if (method_exists($guard, 'setRequest')) {
            $guard->setRequest($this->app->refresh('request', $guard, 'setRequest'));
        }

        return $guard;
    }

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

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

    protected function getConfig($name)
    {
        return $this->app['config']["auth.guards.{$name}"];
    }

    public function getDefaultDriver()
    {
        return $this->app['config']['auth.defaults.guard'];
    }

    public function shouldUse($name)
    {
        $name = $name ?: $this->getDefaultDriver();

        $this->setDefaultDriver($name);

        $this->userResolver = function ($name = null) {
            return $this->guard($name)->user();
        };
    }

    public function setDefaultDriver($name)
    {
        $this->app['config']['auth.defaults.guard'] = $name;
    }

    public function viaRequest($driver, callable $callback)
    {
        return $this->extend($driver, function () use ($callback) {
            $guard = new RequestGuard($callback, $this->app['request'], $this->createUserProvider());

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

    public function userResolver()
    {
        return $this->userResolver;
    }

    public function resolveUsersUsing(Closure $userResolver)
    {
        $this->userResolver = $userResolver;
        return $this;
    }

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

        return $this;
    }

    public function provider($name, Closure $callback)
    {
        $this->customProviderCreators[$name] = $callback;
        return $this;
    }

    public function __call($method, $parameters)
    {
        return $this->guard()->{$method}(...$parameters);
    }
}

在本例中,相当于__call("authenticate", []),这里,$this->guard()就是SessionGuard,它是通过AuthManager的函数resolve('web')得到,

简单说,在AuthManager里面,函数guard()调用了函数resolve('web'),其中又调用了createSessionDriver实例化了SessionGuard.

SessionGuard {#329#name: "web"
  #lastAttempted: null
  #viaRemember: false
  #session: Store {#314 ▶}
  #cookie: CookieJar {#302 ▶}
  #request: Request {#42 ▶}
  #events: Dispatcher {#26 ▶}
  #loggedOut: false
  #recallAttempted: false
  #user: null
  #provider: EloquentUserProvider {#326 ▶}
}

对这个过程深入一点的话,就是在 Illuminate\Auth\AuthManager中,createSessionDriver()调用了SessionGuardsetDispatcher()函数,进行了一次注入(关联),

    // 在这里调用SessionGuard的setDispatcher()函数
    if (method_exists($guard, 'setDispatcher')) {
        $guard->setDispatcher($this->app['events']);
    }

SessionGuardsetDispatcher里,

    public function setDispatcher(Dispatcher $events)
    {
        $this->events = $events;
    }

这里\$events的原型是 Illuminate\Contracts\Events\Dispatcher这个接口,其实例是

\Illuminate\Events\Dispatcher, 这种关联在Illuminate\Foundation\Application中可以看得很明白,

'events' => [ \Illuminate\Events\Dispatcher::class, \Illuminate\Contracts\Events\Dispatcher::class ],

AuthManager并没有authenticate(),所以\Illuminate\Auth\Middleware\Authenticate里面的这一句

$this->guard()->authenticate() 在执行时,实际找不到AuthManager的authenticate(),便会通过AuthManager的

public function __call($method, $parameters)
{
    return $this->guard()->{$method}(...$parameters);
}

来调用SessionGuard里的authenticate()函数,SessionGuard也没有authenticate,它是通过宏use GuardHelpers, Macroable; 调用了Illuminate\Auth\GuardHelpers中的authenticate()

<?php
namespace Illuminate\Auth;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;

trait GuardHelpers
{
    protected $user;
    protected $provider;

    public function authenticate()
    {
        if (! is_null($user = $this->user())) {
            return $user;
        }

        throw new AuthenticationException;
    }

$this->user()这里进入了真正进行身份验证的地方,其他Auth验证函数也是同样的原理,比如你在使用Auth::check()判断一个用户是否已经登录时,实际调用的函数也是Illuminate\Auth\GuardHelpers中的check(),

    public function check()
    {
        return ! is_null($this->user());
    }

同样,这个GuardHelpers中还有Auth::id()Auth::guest()等,

所以,最关键的,还是看在Illuminate\Auth\SessionGuard中的user()函数,如下,

    public function user()
    {
        if ($this->loggedOut) {
            return;
        }

        if (! is_null($this->user)) {
            return $this->user;
        }

        // 从session中获取用户ID。已登录用户的用户ID会被存储在Session内。
        $id = $this->session->get($this->getName());

        if (! is_null($id)) {
             // 通过Illuminate\Auth\EloquentUserProvider 的retrieveById() 方法,
             // 获取用户信息,并返回User Model
             // 换句话说:在这里SessionGuard拿到了用户信息
            if ($this->user = $this->provider->retrieveById($id)) {
                $this->fireAuthenticatedEvent($this->user);
            }
        }

        // 获取认证cookie(登录时选中记住我,服务端会向浏览器设置一个认证cookie,
        // 其中包括 remember_token,下次可以直接使用这个认证cookie进行登录)        
        $recaller = $this->recaller();

        if (is_null($this->user) && ! is_null($recaller)) {
            // 使用 remember_token 从数据库中获取用户信息
            // 最终委托 Illuminate\Auth\EloquentUserProvider::retrieveByToken()方法取数据
            $this->user = $this->userFromRecaller($recaller);

            if ($this->user) {

                //将用户ID保存到Session
                $this->updateSession($this->user->getAuthIdentifier());

                $this->fireLoginEvent($this->user, true);
            }
        }

        return $this->user;
    }

第一步,如何拿到用户id

先看如何从session获取用户id,session接口在Illuminate\Contracts\Session\Session,其门面在Illuminate\Foundation\Application.php中有定义

'session'              => [\Illuminate\Session\SessionManager::class],
'session.store'        => [\Illuminate\Session\Store::class, \Illuminate\Contracts\Session\Session::class],

当然, 真正的session是\Illuminate\Session\Store,这个在session的源码详解中已经讲过,

参考另文:Laravel5.5源码详解 – Session的启动分析

它实现了session的接口,如下

...
namespace Illuminate\Session;
use Illuminate\Contracts\Session\Session;
class Store implements Session
{...}

先是SessionGuard得到一个当前的登陆相关的信息,这里还没有用户的信息

    public function getName()
    {
        return 'login_'.$this->name.'_'.sha1(static::class);
    }

打印出来是如下的结果,

"login_web_59ba36addc2b2f9401580f014c7f58ea4e30989d"

翻译开来就是login_web_Illuminate\Auth\SessionGuard,后面会用作登陆用户信息的识别。

前面说过,session就是\Illuminate\Session\Store,得到用户id在其get函数中,

    public function get($key, $default = null)
    {
        return Arr::get($this->attributes, $key, $default);
    }

进一步追踪,发现用户信息在session加载时就已经放入attributes属性中,

    protected function loadSession()
    {
        $this->attributes = array_merge($this->attributes, $this->readFromHandler());
    }

其中$this->readFromHandler()又是信息来源,

protected function readFromHandler()
{
    if ($data = $this->handler->read($this->getId())) {
        $data = @unserialize($this->prepareForUnserialize($data));

        if ($data !== false && ! is_null($data) && is_array($data)) {
            return $data;
        }
    }

    return [];
}

再id是在这里拿到的,

    public function getId()
    {
        return $this->id;
    }

    public function setId($id)
    {
        $this->id = $this->isValidId($id) ? $id : $this->generateSessionId();
    }

那么又是谁调用了setId来设置用户id呢?是Illuminate\Session\Middleware\StartSession

    public function handle($request, Closure $next)
    {
        $this->sessionHandled = true;

        if ($this->sessionConfigured()) {
            $request->setLaravelSession(
                $session = $this->startSession($request)
            );

            $this->collectGarbage($session);
        }

        $response = $next($request);

        if ($this->sessionConfigured()) {
            $this->storeCurrentUrl($request, $session);

            $this->addCookieToResponse($response, $session);
        }

        return $response;
    }

    protected function startSession(Request $request)
    {
        return tap($this->getSession($request), function ($session) use ($request) {
            $session->setRequestOnHandler($request);

            $session->start();
        });
    }

    public function getSession(Request $request)
    {
        return tap($this->manager->driver(), function ($session) use ($request) {
            $session->setId($request->cookies->get($session->getName()));
        });
    }

可以看到,这个函数调用链的关系为handle() ==> startSession() ==> getSession(),handle是中间件的主函数,对所有经过的request进行处理。所以,当Request创建的时候,中间件实例化后,会首先在这里拿到用户的某个特征信息,并记录相关详情。

第二步,根据用户id获取用户信息

上面拿到了用户id。再来看如何根据这个id获取用户信息。前面注释中提到,$this->provider 是这个
Illuminate\Contracts\Auth\UserProvider 接口,其实现类是
Illuminate\Auth\EloquentUserProvider,它的retrieveById()函数如下,

namespace Illuminate\Auth;

use Illuminate\Support\Str;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Contracts\Hashing\Hasher as HasherContract;
use Illuminate\Contracts\Auth\Authenticatable as UserContract;

class EloquentUserProvider implements UserProvider
{   
    protected $hasher;
    protected $model;

    public function __construct(HasherContract $hasher, $model)
    {
        $this->model = $model;
        $this->hasher = $hasher;
    }

    public function retrieveById($identifier)
    {
        $model = $this->createModel();

        return $model->newQuery()
            ->where($model->getAuthIdentifierName(), $identifier)
            ->first();
    }    

可见,这里实质上是对模型相关的数据库进行了一次查询,然后获取了所有相关属性。要特别注意的是,重点进行查询的函数是这里的first(),上面那三句,

        return $model->newQuery()
            ->where($model->getAuthIdentifierName(), $identifier)
            ->first();

重新写一下,相当于下面的执行语句,

// 实例化一个QueryBuilder
$query = $model->newQuery();
// 执行一些准备工作
$query = $query->where($model->getAuthIdentifierName(), $identifier);
// 查询数据库,获取当前用户属性及相关信息  ---- 这里才是重点
$user = $query->first();
// 获得用户信息之后,返回查询结果
return $user;

关于数据库的查询,详情可查阅另文,里面对来龙去脉进行了非常详尽的描述。

Laravel5.5源码详解 – 一次查询的详细执行:从Auth-Login-web中间件到数据库查询结果的全过程

附:laravel自带的Auth路由参考

如果使用的是laravel默认的Auth路由,那么就是下面的情况,可以根据需要选择。

Illuminate\Routing\Router

public function auth()
{
    // Authentication Routes...
    $this->get('login', 'Auth\LoginController@showLoginForm')->name('login');
    $this->post('login', 'Auth\LoginController@login');
    $this->post('logout', 'Auth\LoginController@logout')->name('logout');

    // Registration Routes...
    $this->get('register', 'Auth\RegisterController@showRegistrationForm')->name('register');
    $this->post('register', 'Auth\RegisterController@register');

    // Password Reset Routes...
    $this->get('password/reset', 'Auth\ForgotPasswordController@showLinkRequestForm')->name('password.request');
    $this->post('password/email', 'Auth\ForgotPasswordController@sendResetLinkEmail')->name('password.email');
    $this->get('password/reset/{token}', 'Auth\ResetPasswordController@showResetForm')->name('password.reset');
    $this->post('password/reset', 'Auth\ResetPasswordController@reset');
}

App\Http\Controllers\Auth\LonginController里面几乎没有有用的信息

class LoginController extends Controller
{
    use AuthenticatesUsers;

其父类App\Http\Controllers\Controller 继承了 Illuminate\Routing\Controller

<?php

namespace App\Http\Controllers;

use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;

class Controller extends BaseController
{
    use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
}

在Illuminate\Foundation\Auth\AuthenticatesUsers.php:

public function showLoginForm()
{
    return view('auth.login');
}

猜你喜欢

转载自blog.csdn.net/tanmx219/article/details/78922770