本文介紹 Laravel 用戶認(rèn)證的使用、基本邏輯和底層的實(shí)現(xiàn)。
快速使用
在 Laravel 框架初始化后,運(yùn)行 php artisan make:auth 和 php artisan migrate 就能啟用 Laravel 自帶的用戶認(rèn)證功能。
- 用戶認(rèn)證的配置文件在
config/auth.php中,
// 默認(rèn)使用的配置。
// guard 是用戶認(rèn)證邏輯的實(shí)現(xiàn)。web 是網(wǎng)站用戶認(rèn)證邏輯;api 是 API 用戶認(rèn)證邏輯
'defaults' => [
'guard' => 'web',
'passwords' => 'users',
],
guard 還需配置對(duì)應(yīng)的 driver 和 provider。
driver 配置對(duì)應(yīng)具體實(shí)現(xiàn) guard 的邏輯類;provider 是邏輯類使用的數(shù)據(jù)提供者(查詢、更新對(duì)應(yīng)的用戶信息的具體實(shí)現(xiàn)類)。
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
...
],
- 認(rèn)證的路由信息在
Illuminate\Routing\Router.php中,通過Illuminate\Support\Facades\Auth的routes()方法引入。注冊和登錄部分如下:
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...
}
由路由信息可知,注冊、登錄對(duì)應(yīng)的控制器和方法。
- 限制認(rèn)證用戶才能訪問
通常我們有一些接口是需要登錄后才能訪問的,在 Laravel 中是給路由分組,并添加中間件來實(shí)現(xiàn)。
// 請求到達(dá)路由中的 Controller 前,會(huì)先通過 auth 中間件驗(yàn)證
Route::middleware('auth')->group(function () {
Route::get('/user', 'UserController@index');
// ....
});
在 App\Http\Kernel 中定義了 auth 中間件
protected $routeMiddleware = [
'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
// ....
]
注冊、登錄、登錄檢測邏輯
注冊邏輯
注冊邏輯在控制器 Auth\RegisterController 的 register 方法中,具體是由控制器中的 RegistersUsers trait 類實(shí)現(xiàn)。
public function register(Request $request)
{
// 調(diào)用控制器中的方法對(duì)參數(shù)進(jìn)行驗(yàn)證,包括 name\email\password
$this->validator($request->all())->validate();
// 調(diào)用控制器中 create 方法創(chuàng)建用戶
// 使用 $user 初始化 Redistered 對(duì)象
// 通過 event() 函數(shù)觸發(fā)注冊事件
event(new Registered($user = $this->create($request->all())));
// 獲取 guard 實(shí)例,并通過 guard 登錄創(chuàng)建的用戶
$this->guard()->login($user);
// 根據(jù) $user 注冊信息進(jìn)行頁面跳轉(zhuǎn)
return $this->registered($request, $user)
?: redirect($this->redirectPath());
}
protected function guard()
{
// 通過 Illuminate\Support\Facades\Auth 獲取對(duì)應(yīng) guard 對(duì)象
return Auth::guard();
}
登錄邏輯
登錄邏輯在控制器 Auth\LoginController 的 login 方法中,具體是由控制器中的 AuthenticatesUsers trait 類實(shí)現(xiàn)。
public function login(Request $request)
{
// 參數(shù)驗(yàn)證, email\password 參數(shù)
$this->validateLogin($request);
// 使用 ThrottlesLogins trait 對(duì)登錄進(jìn)行限制(一分鐘內(nèi),登錄失敗超過配置的次數(shù),將不能進(jìn)行登錄),防止惡意的登錄嘗試
// 限制的依據(jù)是 登錄的 email 和 IP 地址。
if ($this->hasTooManyLoginAttempts($request)) {
// 觸發(fā)登錄鎖定事件
$this->fireLockoutEvent($request);
// 返回鎖定的響應(yīng)信息
return $this->sendLockoutResponse($request);
}
// 根據(jù) $request 中的登錄憑證嘗試登錄
// 這里實(shí)際以是獲取 guard 對(duì)象進(jìn)行登錄嘗試
if ($this->attemptLogin($request)) {
// 登錄成功后,重新生成 session,并跳轉(zhuǎn)到設(shè)置的登錄成功頁面
return $this->sendLoginResponse($request);
}
// 沒登錄成功,增加登錄失敗次數(shù),返回登錄失敗的響應(yīng)
$this->incrementLoginAttempts($request);
return $this->sendFailedLoginResponse($request);
}
protected function attemptLogin(Request $request)
{
// 通過調(diào)用 Illuminate\Support\Facades\Auth 獲取 guard 對(duì)象,并通過 guard 進(jìn)行實(shí)際的登錄邏輯
return $this->guard()->attempt(
$this->credentials($request), $request->has('remember')
);
}
登錄檢測邏輯
中間件中通過 authenticate 方法檢測用戶是否登錄
protected function authenticate(array $guards)
{
// 默認(rèn)時(shí),傳遞的 $guards 為空
if (empty($guards)) {
// 調(diào)用注入的 auth 對(duì)象的 authenticate 方法。auth 會(huì)調(diào)用默認(rèn) guard 的 authenticate 方法
return $this->auth->authenticate();
}
foreach ($guards as $guard) {
// 調(diào)用特定 guard 的 check 方法
if ($this->auth->guard($guard)->check()) {
return $this->auth->shouldUse($guard);
}
}
throw new AuthenticationException('Unauthenticated.', $guards);
}
認(rèn)證底層實(shí)現(xiàn)源碼閱讀
上面 Controller 中的登錄和注冊邏輯最終都是調(diào)用 Illuminate\Support\Facades\Auth 來獲取 guard ,并通過 guard 來進(jìn)行實(shí)際的登錄、注冊邏輯。
Illuminate\Support\Facades\Auth 是應(yīng)用為 Illuminate\Auth\AuthManager 類提供的一個(gè)靜態(tài)的接口,所以應(yīng)用最終是使用 AuthManager 中的 guard() 方法來獲取 guard 實(shí)例的。Facade 的原理可以參考文檔。
下面為獲取 guard 實(shí)例的主要邏輯:
public function guard($name = null)
{
// 如果沒有傳 $name,就獲取默認(rèn)的 guard 。 默認(rèn)的為配置中的 web
$name = $name ?: $this->getDefaultDriver();
// 如果 guards 已經(jīng)被實(shí)例化的,就直接調(diào)用,否則通過 resolve 方法創(chuàng)建 guard 對(duì)象
return isset($this->guards[$name])
? $this->guards[$name]
: $this->guards[$name] = $this->resolve($name);
}
protected function resolve($name)
{
// 獲取名稱對(duì)應(yīng)的 guard 配置,前面說了,包括 driver\provider
$config = $this->getConfig($name);
// 配置異常判斷
if (is_null($config)) {
throw new InvalidArgumentException("Auth guard [{$name}] is not defined.");
}
// 如果有設(shè)置自定義的 guard 創(chuàng)建方法,則用自定義的方法創(chuàng)建
// 自定義的方法通過類中的 extend 方法定義
if (isset($this->customCreators[$config['driver']])) {
return $this->callCustomCreator($name, $config);
}
$driverMethod = 'create'.ucfirst($config['driver']).'Driver';
// 根據(jù)配置,判斷是否有對(duì)應(yīng)創(chuàng)建 guard 的方法,有則調(diào)用
// 系統(tǒng)自帶 createSessionDriver\createTokenDriver 兩個(gè)創(chuàng)建 guard 方法
if (method_exists($this, $driverMethod)) {
return $this->{$driverMethod}($name, $config);
}
// 拋出 guard driver 沒有定義的異常
throw new InvalidArgumentException("Auth guard driver [{$name}] is not defined.");
}
默認(rèn)情況下,web guard 的 driver 為 session。所以是通過 createSessionDriver 方法將會(huì)返回一個(gè) SessionGuard 實(shí)例。然后通過 SessionGuard 實(shí)例進(jìn)行用戶認(rèn)證邏輯。
這里最靈活的地方在于還提供了一個(gè) extend 方法來自定義 guard。使得可以方便的擴(kuò)展自己的用戶認(rèn)證。
下面是用戶認(rèn)證的相關(guān)的類圖。

最后
有問題,歡迎留言交流。
參考文檔
https://laravel-china.org/docs/laravel/5.4/authentication/1239