預計閱讀 12 分鐘。
本文介紹 Laravel 用戶認證的使用、基本邏輯和底層的實現。
快速使用
在 Laravel 框架初始化后,運行 php artisan make:auth
和 php artisan migrate
就能啟用 Laravel 自帶的用戶認證功能。
- 用戶認證的配置文件在
config/auth.php
中,
1// 默認使用的配置。
2// guard 是用戶認證邏輯的實現。web 是網站用戶認證邏輯;
3// api 是 API 用戶認證邏輯
4'defaults' => [
5 'guard' => 'web',
6 'passwords' => 'users',
7],
guard
還需配置對應的 driver
和 provider
。
driver
配置對應具體實現 guard
的邏輯類;provider
是邏輯類使用的數據提供者(查詢、更新對應的用戶信息的具體實現類)。
1'guards' => [
2 'web' => [
3 'driver' => 'session',
4 'provider' => 'users',
5 ],
6 ...
7 ],
- 認證的路由信息在
Illuminate/Routing/Router.php
中,通過Illuminate/Support/Facades/Auth
的routes()
方法引入。註冊和登錄部分如下:
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 }
由路由信息可知,註冊、登錄對應的控制器和方法。
- 限制認證用戶才能訪問
通常我們有一些介面是需要登錄后才能訪問的,在 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/RegisterController
的 register
方法中,具體是由控制器中的 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/LoginController
的 login
方法中,具體是由控制器中的 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。使得可以方便的擴展自己的用戶認證。
下面是用戶認證的相關的類圖。
最後
有問題,歡迎留言交流。
參考文檔
https://laravel-china.org/docs/laravel/5.4/authentication/1239