Laravel 用戶認證邏輯

預計閱讀 12 分鐘。

本文介紹 Laravel 用戶認證的使用、基本邏輯和底層的實現。

快速使用

在 Laravel 框架初始化后,運行 php artisan make:auth  和   php artisan migrate  就能啟用 Laravel 自帶的用戶認證功能。

  1. 用戶認證的配置文件在 config/auth.php 中,
1// 默認使用的配置。
2// guard 是用戶認證邏輯的實現。web 是網站用戶認證邏輯;
3// api 是 API 用戶認證邏輯
4'defaults' => [
5        'guard' => 'web',
6        'passwords' => 'users',
7],

guard 還需配置對應的 driverprovider
driver 配置對應具體實現 guard 的邏輯類;provider 是邏輯類使用的數據提供者(查詢、更新對應的用戶信息的具體實現類)。

1'guards' => [
2        'web' => [
3            'driver' => 'session',
4            'provider' => 'users',
5        ],
6        ...
7    ],
  1. 認證的路由信息在 Illuminate/Routing/Router.php 中,通過 Illuminate/Support/Facades/Authroutes() 方法引入。註冊和登錄部分如下:
 1public function auth()
 2{
 3        // Authentication Routes...
 4        $this->get('login''Auth/LoginController@showLoginForm')->name('login');
 5        $this->post('login''Auth/LoginController@login');
 6        $this->post('logout''Auth/LoginController@logout')->name('logout');
 7
 8        // Registration Routes...
 9        $this->get('register''Auth/RegisterController@showRegistrationForm')->name('register');
10        $this->post('register''Auth/RegisterController@register');
11
12        // Password Reset Routes...
13 }

由路由信息可知,註冊、登錄對應的控制器和方法。

  1. 限制認證用戶才能訪問
    通常我們有一些介面是需要登錄后才能訪問的,在 Laravel 中是給路由分組,並添加中間件來實現。
1// 請求到達路由中的 Controller 前,會先通過 auth 
2// 中間件驗證
3Route::middleware('auth')->group(function () {
4    Route::get('/user''UserController@index');
5    // ....
6});

App/Http/Kernel 中定義了 auth 中間件

1 protected $routeMiddleware = [
2        'auth' => /Illuminate/Auth/Middleware/Authenticate::class,
3        // ....
4]

註冊、登錄、登錄檢測邏輯

註冊邏輯

註冊邏輯在控制器 Auth/RegisterControllerregister 方法中,具體是由控制器中的 RegistersUsers   trait 類實現。

 1public function register(Request $request)
 2{
 3     // 調用控制器中的方法對參數進行驗證,包括 
 4     // name/email/password
 5     $this->validator($request->all())->validate();
 6     // 調用控制器中 create 方法創建用戶
 7     // 使用 $user 初始化 Redistered 對象
 8     // 通過 event() 函數觸發註冊事件
 9     event(new Registered($user = $this->create($request->all())));
10
11     // 獲取 guard 實例,並通過 guard 登錄創建的用戶
12     $this->guard()->login($user);
13     // 根據 $user 註冊信息進行頁面跳轉
14     return $this->registered($request, $user)
15                        ?: redirect($this->redirectPath());
16}
17
18protected function guard()
19{
20    // 通過 Illuminate/Support/Facades/Auth 
21    // 獲取對應 guard 對象
22    return Auth::guard();
23}

登錄邏輯

登錄邏輯在控制器 Auth/LoginControllerlogin 方法中,具體是由控制器中的 AuthenticatesUsers   trait 類實現。

 1public function login(Request $request)
 2{
 3   // 參數驗證, email/password 參數
 4   $this->validateLogin($request);
 5
 6   // 使用 ThrottlesLogins trait 對登錄進行限制(
 7   // 一分鐘內,登錄失敗超過配置的次數,將不能進行登錄),
 8   // 防止惡意的登錄嘗試 
 9   // 限制的依據是 登錄的 email 和 IP 地址。
10   if ($this->hasTooManyLoginAttempts($request)) {
11        // 觸發登錄鎖定事件
12        $this->fireLockoutEvent($request);
13        // 返回鎖定的響應信息
14        return $this->sendLockoutResponse($request);
15   }
16   // 根據 $request 中的登錄憑證嘗試登錄
17   // 這裡實際以是獲取 guard 對象進行登錄嘗試 
18   if ($this->attemptLogin($request)) {
19       // 登錄成功后,重新生成 session,
20       // 並跳轉到設置的登錄成功頁面
21       return $this->sendLoginResponse($request);
22   }
23
24   // 沒登錄成功,增加登錄失敗次數,返回登錄失敗的響應
25   $this->incrementLoginAttempts($request);
26
27   return $this->sendFailedLoginResponse($request);
28}
29
30protected function attemptLogin(Request $request)
31{
32    // 通過調用 Illuminate/Support/Facades/Auth 
33    // 獲取 guard 對象,並通過 guard 進行實際的登錄邏輯
34    return $this->guard()->attempt(
35       $this->credentials($request), $request->has('remember')
36    );
37}

登錄檢測邏輯

中間件中通過 authenticate 方法檢測用戶是否登錄

 1protected function authenticate(array $guards)
 2{  
 3    // 默認時,傳遞的 $guards 為空
 4    if (empty($guards)) {
 5        // 調用注入的 auth 對象的 authenticate 方法。
 6        // auth 會調用默認 guard 的 authenticate 方法
 7        return $this->auth->authenticate();
 8    }
 9
10    foreach ($guards as $guard) {
11        // 調用特定 guard 的 check 方法
12        if ($this->auth->guard($guard)->check()) {
13            return $this->auth->shouldUse($guard);
14        }
15    }
16
17    throw new AuthenticationException('Unauthenticated.', $guards);
18}

認證底層實現源碼閱讀

上面 Controller 中的登錄和註冊邏輯最終都是調用 Illuminate/Support/Facades/Auth 來獲取 guard ,並通過 guard 來進行實際的登錄、註冊邏輯。
Illuminate/Support/Facades/Auth 是應用為 Illuminate/Auth/AuthManager 類提供的一個靜態的介面,所以應用最終是使用 AuthManager 中的 guard() 方法來獲取 guard 實例的。Facade 的原理可以參考文檔。
下面為獲取 guard   實例的主要邏輯:

 1public function guard($name = null)
 2{
 3    // 如果沒有傳 $name,就獲取默認的 guard 。 
 4    // 默認的為配置中的 web
 5    $name = $name ?: $this->getDefaultDriver();
 6    // 如果 guards 已經被實例化的,就直接調用,否則通過
 7    // resolve 方法創建 guard 對象
 8    return isset($this->guards[$name])
 9                ? $this->guards[$name]
10                : $this->guards[$name] = $this->resolve($name);
11}
12
13protected function resolve($name)
14{
15    // 獲取名稱對應的 guard 配置,
16    // 前面說了,包括 driver/provider
17    $config = $this->getConfig($name);
18    // 配置異常判斷
19    if (is_null($config)) {
20        throw new InvalidArgumentException("Auth guard [{$name}] is not defined.");
21    }
22    // 如果有設置自定義的 guard 創建方法,則用自定義的方法創建
23    // 自定義的方法通過類中的 extend 方法定義
24    if (isset($this->customCreators[$config['driver']])) {
25        return $this->callCustomCreator($name, $config);
26    }
27
28    $driverMethod = 'create'.ucfirst($config['driver']).'Driver';
29    // 根據配置,判斷是否有對應創建 guard 的方法,有則調用
30    // 系統自帶 createSessionDriver/createTokenDriver 了兩個
31    // 創建 guard 方法 
32    if (method_exists($this, $driverMethod)) {
33        return $this->{$driverMethod}($name, $config);
34    }
35    // 拋出 guard driver 沒有定義的異常
36    throw new InvalidArgumentException("Auth guard driver [{$name}] is not defined.");
37}

默認情況下,web guard 的 driver 為 session。所以是通過 createSessionDriver 方法將會返回一個 SessionGuard 實例。然後通過 SessionGuard 實例進行用戶認證邏輯。
這裡最靈活的地方在於還提供了一個 extend 方法來自定義 guard。使得可以方便的擴展自己的用戶認證。
下面是用戶認證的相關的類圖。

用戶認證類圖.png

最後

有問題,歡迎留言交流。

參考文檔

https://laravel-china.org/docs/laravel/5.4/authentication/1239