Laravel 源码解读系列第四篇-Auth 机制
前言
Laravel
有一个神器:
php artisan make:auth
能够快速的帮我们完成一套注册和登录的认证机制,但是这套机制具体的是怎么跑起来的呢?我们不妨来一起看看他的源码。不过在这篇文章中,我只会阐述大致的流程,至于一些具体的细节,比如他的登录次数限制是怎么完成的之类的不妨自己去寻找答案。
源码解读系列,有兴趣的小伙伴可以点个star,我会持续更新各个部分的解读,也是和大家一起进步的一个过程,如有写的不对的地方还望指出。
过程
路由
当我们执行完命令之后,我们会发现,在routes/web.php
中多了这样一行代码:
Auth::routes();
结合我们在前面讲到的Facades,我们会执行Facades/Auth.php
的routes
方法:
public static function routes()
{
static::$app->make('router')->auth();
}
而$app->make('router')
会返回一个Routing/Router.php
对象的实例,而他的auth
方法是:
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');
}
而这里的get
和post
方法,其实也就是我们通过Route::get
等最终会调用的方法。
注册
我们直接看表单提交的方法:
$this->post('register', 'Auth\RegisterController@register');
//RegisterController.php
class RegisterController extends Controller
{
/*
|--------------------------------------------------------------------------
| Register Controller
|--------------------------------------------------------------------------
|
| This controller handles the registration of new users as well as their
| validation and creation. By default this controller uses a trait to
| provide this functionality without requiring any additional code.
|
*/
use RegistersUsers;
/**
* Where to redirect users after registration.
*
* @var string
*/
protected $redirectTo = '/home';
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest');
}
/**
* Get a validator for an incoming registration request.
*
* @param array $data
* @return \Illuminate\Contracts\Validation\Validator
*/
protected function validator(array $data)
{
return Validator::make($data, [
'name' => 'required|string|max:255',
'email' => 'required|string|email|max:255|unique:users',
'password' => 'required|string|min:6|confirmed',
]);
}
/**
* Create a new user instance after a valid registration.
*
* @param array $data
* @return User
*/
protected function create(array $data)
{
return User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => bcrypt($data['password']),
]);
}
}
里面使用到了RegistersUsers
这个trait
,里面就有我们需要的register
方法:
public function register(Request $request)
{
$this->validator($request->all())->validate();
event(new Registered($user = $this->create($request->all())));
// 默认情况下,$this->guard()会返回一个`SessionGuard`的对象
$this->guard()->login($user);
return $this->registered($request, $user)
?: redirect($this->redirectPath());
}
protected function guard()
{
return Auth::guard();
}
其中最核心的部分就是:
$this->guard()->login($user);
而$this->guard()
在默认配置下会返回一个SessionGuard
的对象,具体是这样实现的:
前面我们有讲过,当我们执行Facades
下一个不存在的方法时,我们会调用Facade.php
的__callStatic
方法,这个方法会获取当前对象的getFacadeAccessor
方法而返回一个对应的对象并调用他所需要调用的方法,这个详细过程在我的第三篇中都有所阐述。
而auth
的别名绑定,是在我们初始化的过程中绑定的,具体可以看我写的第一篇,所以,这里我们会调用AuthManager
这个对象:
public function guard($name = null)
{
$name = $name ?: $this->getDefaultDriver();
return isset($this->guards[$name])
? $this->guards[$name]
: $this->guards[$name] = $this->resolve($name);
}
public function getDefaultDriver()
{
return $this->app['config']['auth.defaults.guard'];
}
protected function resolve($name)
{
$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);
}
$driverMethod = 'create'.ucfirst($config['driver']).'Driver';
if (method_exists($this, $driverMethod)) {
return $this->{$driverMethod}($name, $config);
}
throw new InvalidArgumentException("Auth guard driver [{$name}] is not defined.");
}
//config/auth.php
'defaults' => [
'guard' => 'web',
'passwords' => 'users',
],
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'token',
'provider' => 'users',
],
],
通过调用AuthManager
可以拼接得出,最终他会调用一个createSessionDriver
方法:
public function createSessionDriver($name, $config)
{
$provider = $this->createUserProvider($config['provider']);
$guard = new SessionGuard($name, $provider, $this->app['session.store']);
if (method_exists($guard, 'setCookieJar')) {
$guard->setCookieJar($this->app['cookie']);
}
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;
}
所以最终是调用了SessionGuard
的login
方法来完成session
部分的功能。
登录
展示表单的部分就不赘述了,这里我们直接看:
$this->post('login', 'Auth\LoginController@login');
//LoginController.php
class LoginController extends Controller
{
use AuthenticatesUsers;
/**
* Where to redirect users after login.
*
* @var string
*/
protected $redirectTo = '/home';
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest')->except('logout');
}
}
他使用到了一个AuthenticatesUsers
trait,里面有一个login
方法,也就是我们要使用到的login
了:
public function login(Request $request)
{
$this->validateLogin($request);
if ($this->hasTooManyLoginAttempts($request)) {
$this->fireLockoutEvent($request);
return $this->sendLockoutResponse($request);
}
if ($this->attemptLogin($request)) {
return $this->sendLoginResponse($request);
}
$this->incrementLoginAttempts($request);
return $this->sendFailedLoginResponse($request);
}
其实大部分和register
的部分差不多,核心部分还是在于SessionGuard
对象的获取,这里就不过多赘述了,不过值得一提的是,像Auth::check()
等很多方法的使用,其实也会通过SessionGuard
来完成,主要是通过:
public function __call($method, $parameters)
{
return $this->guard()->{$method}(...$parameters);
}
public function check()
{
return ! is_null($this->user());
}
来得以完成调度。