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
,因此会直接去调用authManager
的authenticate
方法,再经过魔术方法__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']
了。那这样就会调用sessionGuard
的check
方法来检查用户是否登录:
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 协议》,转载必须注明作者和本文链接
推荐文章: