auth 中间件是如何起作用的源码阅读笔记

前言

Laravel 提供的 auth 中间件在过滤指定动作时,如该用户未通过身份验证(未登录用户),将会被重定向到登录页面。

源码笔记

定义位置

auth 中间件是\Illuminate\Auth\Middleware\Authenticate::class 的别名,定义在 app/Http/Kernel.php中。

注册时机

它可以注册在控制器上。
app/Http/Controllers/UsersController.php

    public function __construct()
    {
        $this->middleware('auth', ['except' => ['show']]);
    }

middleware方法的第一参数为中间件名称。

第二参数为option数组,有except和only两种选择,其中only的优先级更高。

如果有only,那么表示控制器中只有only指定的方法才会应用此中间件。

如果没有only,但是有except,那么表示控制器中except指定的方法将不会应用此中间件。

还可以注册在路由上。但是这样没有注册在控制器上那么方便。
routes/web.php

Route::get('users/{user}/edit', 'UsersController@edit')->name('users.edit')->middleware('auth')

起作用的过程

从生命周期的知识可以知道,request对象经过第一批全局中间件后,会从路由和控制器上再次提取出第二批中间件,auth中间件正是其中之一。

假如是一个未登陆的用户,访问users/1/edit这个uri,那么在经过auth中间件时,会进入其handle方法:
vendor/laravel/framework/src/Illuminate/Auth/Middleware/Authenticate.php

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

        return $next($request);
    }
    protected function authenticate(array $guards)
    {
        if (empty($guards)) {
            return $this->auth->authenticate();
        }

        foreach ($guards as $guard) {
            if ($this->auth->guard($guard)->check()) {
                return $this->auth->shouldUse($guard);
            }
        }

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

不指定中间件的额外参数

我上面并没有指定中间件的额外参数$guards,因此会直接去调用authManagerauthenticate方法,再经过魔术方法__call,最后实际上调用了sessionGuard中的GuardHelpers trait的authenticate方法来检查用户是否已登录。

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

        throw new AuthenticationException;
    }

这里就是指如果当前用户为null(未登录),那么就抛出AuthenticationException

指定中间件的额外参数为web

比如在定义路由时:
routes/web.php

Route::get('users/{user}/edit', 'UsersController@edit')->name('users.edit')->middleware('auth:web');

或者在控制器中也可以绑定:
app/Http/Controllers/UsersController.php

    public function __construct()
    {
        $this->middleware('auth:web', ['except' => ['show']]);
    }

那么在请求穿越auth中间件前,会在管道中进行管道字符串的解析,将中间件和参数分开:
vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php

    protected function parsePipeString($pipe)
    {
        list($name, $parameters) = array_pad(explode(':', $pipe, 2), 2, []);

        if (is_string($parameters)) {
            $parameters = explode(',', $parameters);
        }

        return [$name, $parameters];
    }

可以看到,冒号前的是中间件名,冒号后的是中间件的参数,多个参数以逗号分隔。

这样进入auth中间件的handle方法时,$guards参数就是['web']了。那这样就会调用sessionGuardcheck方法来检查用户是否登录:

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

如果没有登录,那么上面返回false,这样还是会抛出AuthenticationException
\Illuminate\Auth\Middleware\Authenticate::authenticate

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

指定中间件的额外参数为api

过程跟上面类似,区别在于判断用户是否登录时,提取用户信息是从token中去获取。

异常处理

由于未登录都会抛出AuthenticationException异常,那么框架会对其进行特殊处理,最后会将此异常转换为一个普通的响应返回给用户。
vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/Handler.php

    protected function unauthenticated($request, AuthenticationException $exception)
    {
        return $request->expectsJson()
                    ? response()->json(['message' => $exception->getMessage()], 401)
                    : redirect()->guest(route('login'));
    }

由于请求并不是ajax或者pjax或者明确要求返回json,那么就会去调用redirect()->guest(route('login')):
vendor/laravel/framework/src/Illuminate/Routing/Redirector.php

    public function guest($path, $status = 302, $headers = [], $secure = null)
    {
        $this->session->put('url.intended', $this->generator->full());

        return $this->to($path, $status, $headers, $secure);
    }

这样就把当前请求的url存入session中,然后通过一个302跳转到登录页面。由于保存了url.intended到session,那么下次登录后将返回当前请求的url。

小结

  • 通过阅读源码,比较透彻的理解了框架的一些基本用法。
  • 对照文档去分析源码,测试对比不同的条件下的不同流程走向,可以理解的更全面。
本作品采用《CC 协议》,转载必须注明作者和本文链接
日拱一卒
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 1

感谢解惑,先占个位置。我从【不指定中间件的额外参数#】就看不懂了,不过后续应该会遇到,届时再来看

2年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!
未填写
文章
93
粉丝
85
喜欢
153
收藏
121
排名:71
访问:11.4 万
私信
所有博文
社区赞助商