Laravel 官方提供了一个完整的权限验证模块,通过阅读这个小系统的代码,可以一窥 Laravel 的核心设计机制
安装
官方有详细的这个模块的使用文档:
https://laravel.com/docs/5.5/authentication
安装方式:artisan make:auth
生成一个 Middleware 文件:
app/Http/Middleware/RedirectIfAuthenticated.php
和四个 Controller 文件:
app/Http/Controllers/Auth/ForgotPasswordController.php
app/Http/Controllers/Auth/LoginController.php
app/Http/Controllers/Auth/RegisterController.php
app/Http/Controllers/Auth/ResetPasswordController.php
然后在 app/Http/Kernel.php 中完成对中间件的注册:
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, // 在这行注册
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
代码阅读
中间件 RedirectIfAuthenticated.php 代码不多,核心代码是:
Auth::guard($guard)->check()
你会发现其实 Auth(Illuminate\Support\Facades\Auth) 类并没有 guard() 这个方法,而是调用基类 Facade 中的 __callStatic() 方法来间接调用名为 'auth' 的 Service Container,官方文档中有几者之间的对应关系,以及所指向的入口文件:
根据上面的对应关系,会激活这个类:Illuminate\Auth\AuthManager 然后调用里面的 guard() 方法:
public function guard($name = null)
{
$name = $name ?: $this->getDefaultDriver();
return isset($this->guards[$name])
? $this->guards[$name]
: $this->guards[$name] = $this->resolve($name);
}
上面的代码主要是为了选定好 guard 然后返回(guard 是 Laravel 的权限设定机制,默认是 web+session,你可以通过设置 config/auth.php 中的配置项来使用自定义的 guard)
再看一眼本文开始的这行代码:
Auth::guard($guard)->check()
在选定好 guard 以后,调用 guard 的 check() 方法,代码可以在 GuardHelpers.php 里面找到:
/**
* Determine if the current user is authenticated.
*
* @return bool
*/
public function check()
{
return ! is_null($this->user());
}
如果你用的是 Session,那么判断用户登录状态的代码会在这个位置:
/src/Illuminate/Auth/SessionGuard.php
public function user()
{
if ($this->loggedOut) {
return;
}
// 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;
}
$id = $this->session->get($this->getName());
// First we will try to load the user using the identifier in the session if
// one exists. Otherwise we will check for a "remember me" cookie in this
// request, and if one exists, attempt to retrieve the user using that.
if (! is_null($id)) {
if ($this->user = $this->provider->retrieveById($id)) {
$this->fireAuthenticatedEvent($this->user);
}
}
// If the user is null, but we decrypt a "recaller" cookie we can attempt to
// pull the user data on that cookie which serves as a remember cookie on
// the application. Once we have a user we can return it to the caller.
$recaller = $this->recaller();
if (is_null($this->user) && ! is_null($recaller)) {
$this->user = $this->userFromRecaller($recaller);
if ($this->user) {
$this->updateSession($this->user->getAuthIdentifier());
$this->fireLoginEvent($this->user, true);
}
}
return $this->user;
}
Auth 路由注册
在 /routes/web.php 中轻飘飘的写了一句 Auth::routes();
实际上完成了 Auth 相关的一些路由注册,位置在 /Illuminate/Routing/Router.php
内容为
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');
}
关于 Facade
Facade 是一套配合 Service Container 的静态方法解决方案,是一套设计得非常优雅的机制,是 Laravel 的核心机制之一:
Facades provide a "static" interface to classes that are available in the application's service container. Laravel ships with many facades which provide access to almost all of Laravel's features. Laravel facades serve as "static proxies" to underlying classes in the service container, providing the benefit of a terse, expressive syntax while maintaining more testability and flexibility than traditional static methods.