一、前期準備
生成資料表
在.env
檔案中配置好資料庫,執行php artisan migrate
,此時會在資料庫生成我們需要的幾張表
生成檔案
安裝laravel
的ui
包
composer require laravel/ui
生成 登入/註冊 腳手架
php artisan ui vue --auth
此處可以使用vue
或者react
或者bootstrap
,在執行該命令前可以觀察一下resources/views
目錄和routes/web.php
檔案內容。執行完這條命令,就會發現在根目錄下多了一個webpack.mix.js
檔案,/routes/web.php
檔案多了以下內容:
Auth::routes();
Route::get('/home', 'HomeController@index')->name('home');
/resources/views
目錄下多了auth
資料夾、layouts
資料夾和home.blade.php
檔案。
此時,在瀏覽器中輸入yourdomain/home
會發現自動跳轉到login
頁面了,例如我本地的開發域名設定為laravel.do
,開啟laravel.do/home
就會跳轉到laravel.do/login
,不過樣式...很醜!開啟除錯視窗,發現報錯找不到app.css
和app.js
檔案,因為我們根本還沒有生成。不急,接下來先美化一下樣式。
利用Laravel Mix美化頁面
Laravel Mix
是什麼就不解釋,不知道的同學可以看文件哦!使用Laravel Mix
之前要保證已經安裝了nodejs
和npm
:
node -v
npm -v
如果能看到各自的版本號就代表已經安裝了,如果沒有,自行安裝。接著執行:
npm install
安裝完成後,即可執行Mix
:
// 執行 Mix 任務
npm run dev
// 執行所有的 Mix 任務並最小化輸出結果
npm run production
這裡我們就執行npm run production
吧,發現public
下多了css/app.css
和js/app.js
還有mix-manifest.json
,mix-manifest.json
是mix
生成的檔案清單,可是css/app.css
和js/app.js
是從哪兒來的呢?上面提到根目錄多了一個檔案,對,就是webpack.mix.js
,裡面有這麼一段程式碼:
mix.js('resources/js/app.js', 'public/js')
.sass('resources/sass/app.scss', 'public/css');
這段程式碼的意思就是將resources/js/app.js
編譯到public/js
下並命名為app.js
且將resources/sass/app.scss
編譯到public/css
下並命名為app.css
,此時再開啟頁面,真好看!
二、分析認證
先從路由說起
可能有點囉嗦,其實就是為了說明如何得到那一堆路由的,不想看的可直接跳過。
前面說到routes/web.php
檔案增加了兩行程式碼,第二行大家都知道,那麼第一行的Auth::routes();
是什麼鬼?
不急,我們知道Auth
使用了門臉方式,那麼一定有一個Auth.php
檔案在名稱空間Illuminate\Support\Facades
下。
名稱空間從vendor/laravel/framework/src
開始,繼續往下找Illuminate/Support/Facades
,果然找到了Auth.php
檔案,內容不多,一個Auth
類繼承了Facade
,實現了兩個方法,第一個方法getFacadeAccessor
定義了一個門臉訪問器,返回auth
實現可以透過auth()
輔助函式方式來訪問Auth
,第二個方法routes
就是用來生成認證路由的。
routes
方法中只有一行程式碼:
static::$app->make('router')->auth($options);
static::$app
是Illuminate\Contracts\Foundation\Application
的一個例項。
Illuminate\Contracts\Foundation\Application
又繼承自Illuminate\Contracts\Container\Container
,故而static::$app
可以呼叫make
方法。make
方法利用PHP反射機制
解析出一個類,這裡解析出的就是Illuminate\Routing\Router
這個類,所以上面那一行程式碼最終執行的是Illuminate\Routing\Router
類中的auth
方法。
再來看auth
方法。
這個方法就簡單多了,裡面定義了幾種路由,所以Auth::routes();
最終得到的就是這樣的一堆路由:
Route::get('login', 'Auth\LoginController@showLoginForm')->name('login');
Route::post('login', 'Auth\LoginController@login');
Route::post('logout', 'Auth\LoginController@logout')->name('logout');
Route::get('register', 'Auth\RegisterController@showRegistrationForm')->name('register');
Route::post('register', 'Auth\RegisterController@register');
Route::get('password/reset', 'Auth\ForgotPasswordController@showLinkRequestForm')->name('password.request');
Route::post('password/email', 'Auth\ForgotPasswordController@sendResetLinkEmail')->name('password.email');
Route::get('password/reset/{token}', 'Auth\ResetPasswordController@showResetForm')->name('password.reset');
Route::post('password/reset', 'Auth\ResetPasswordController@reset')->name('password.update');
Route::get('password/confirm', 'Auth\ConfirmPasswordController@showConfirmForm')->name('password.confirm');
Route::post('password/confirm', 'Auth\ConfirmPasswordController@confirm');
Route::get('email/verify', 'Auth\VerificationController@show')->name('verification.notice');
Route::get('email/verify/{id}/{hash}', 'Auth\VerificationController@verify')->name('verification.verify');
Route::post('email/resend', 'Auth\VerificationController@resend')->name('verification.resend');
看著這一堆讓人頭大的路由,還是覺得Auth::routes()
好啊!
再聊聊控制器
1、HomeController
HomeController
的建構函式定義了訪問中介軟體auth
。
在Kernel.php
中的$routeMiddleware
可以看到auth
對映的是App\Http\Middleware\Authenticate
類,這個類繼承了Illuminate\Auth\Middleware\Authenticate
類,並定義了redirectTo
方法。
開啟Illuminate\Auth\Middleware\Authenticate
類,我們可以看到這個類很簡單,先是注入了Illuminate\Contracts\Auth\Factory
的物件,然後在handle
方法中呼叫了自身的authenticate
方法,如果認證成功則返回使用者,認證失敗再呼叫unauthenticated
,該方法會丟擲AuthenticationException
異常,將redirectTo
方法返回的結果作為第三個引數傳入其中。AuthenticationException
會呼叫redirectTo
方法進行跳轉。
關於為什麼會呼叫redirectTo
進行跳轉,可以閱讀一下這個類Illuminate\Foundation\Exceptions\Handler
。
此時再看Illuminate\Auth\Middleware\Authenticate
就清楚多了。
protected function redirectTo($request)
{
if (! $request->expectsJson()) {
return route('login');
}
}
redirectTo
方法覆蓋了父類方法,定義了未認證使用者如何跳轉。此處的意思是如果我們沒有期望返回值是json
資料的話就返回命名為login
的路由即:
Route::get('login', 'Auth\LoginController@showLoginForm')->name('login');
2、LoginController
LoginController
的建構函式定義了除了logout
路由其它都訪問guest
這個中介軟體。
在Kernel.php
中可以看到guest
對映的是App\Http\Middleware\RedirectIfAuthenticated
類,該類就做一件事,判斷使用者如果已經登入則跳轉到/home
。
LoginController
還定義了一個保護屬性$redirectTo
和使用了一個性狀AuthenticatesUsers
。
開啟Illuminate\Foundation\Auth\AuthenticatesUsers
會發現,這個性狀就是一個使用者登入的完整的一套邏輯:顯示檢視、獲取資料、驗證資料、進行登入、登入成功響應、登入失敗響應、定義門衛、定義認證欄位等。該性狀內部還使用了RedirectsUsers
和ThrottlesLogins
性狀,前者的作用是重定向後者作用是限流。
在LoginController
中我們可以重定義showLoginForm
方法來顯示其他檢視,可以重定義validateLogin
方法來規定資料驗證規則,可以重定義username
方法來指定要認證的欄位,可以重定義guard
方法來重定義門衛等。
不過,目前資料庫中是空的,先點選頁面右上方的Register
去註冊個使用者吧!點選之後,我們還是先想想Laravel
做了什麼吧!
3、RegisterController
RegisterController
的建構函式定義了訪問中介軟體guest
,定義了跳轉地址$redirectTo = '/home'
,定義了一個資料驗證器validator
和生成使用者記錄,同時也使用一個性狀RegistersUsers
。
照例開啟Illuminate\Foundation\Auth\RegistersUsers
,再看這個性狀就容易多了,也是定義註冊使用者的一套邏輯:顯示檢視、定義門衛、進行註冊、已註冊處理。進行註冊的方法register
內部分為4步:資料驗證、註冊成功事件監聽、使用者登入,註冊成功後頁面跳轉。
註冊成功事件監聽使用的是Illuminate\Auth\Events\Registered
類,在App\Providers\EventServiceProvider
服務提供者的監聽器中定義了事件對映:
protected $listen = [
Registered::class => [
SendEmailVerificationNotification::class,
],
];
當Registered
事件完成時,觸發SendEmailVerificationNotification
,顧名思義就是傳送郵箱驗證通知。
接著我們在註冊頁面輸入好資訊,點選Register
頁面直接跳轉到了/home
,現在去我們剛剛註冊的郵箱裡看下,按道理來說是應該收到一封郵件驗證郵箱的郵件的,但現在什麼也沒有,這是怎麼回事呢?
開啟Illuminate\Auth\Listeners\SendEmailVerificationNotification
看看就知道了。
public function handle(Registered $event)
{
if ($event->user instanceof MustVerifyEmail && ! $event->user->hasVerifiedEmail()) {
$event->user->sendEmailVerificationNotification();
}
}
我們建立的使用者必須是介面Illuminate\Contracts\Auth\MustVerifyEmail
的例項且郵箱還沒有被驗證才會傳送郵件。
開啟模型App\User
我們會發現無論是App\User
本身還是他繼承的Illuminate\Foundation\Auth\User
都沒有實現介面Illuminate\Contracts\Auth\MustVerifyEmail
,所以才沒有傳送驗證郵件,怎麼改造一下就可以送了,自己動動腦筋吧!
到這裡,我們就已經搞清楚使用者註冊登入的整個過程了,去登入頁輸入自己剛剛註冊的使用者,即可成功登入。接下來就該說說忘記密碼這一塊的邏輯了。
我們點選Forgot Your Password?
發現跳轉到了laravel.do/password/reset
,輸入之前註冊使用者時使用的郵箱,點選Send Password Reset Link
,等了一會兒頁面直接報了一個錯:
Expected response code 250 but got code "530", with message "530 5.7.1 Authentication required
"
這是怎麼回事呢?
4、ForgotPasswordController
password/reset
訪問的是ForgotPasswordController
控制器,可是ForgotPasswordController
簡單的讓人頭皮發麻,就使用了一個性狀SendsPasswordResetEmails
。不慌,開啟這個性狀你就會鎮定多了,依舊是Laravel
實現的一套完整的忘記密碼的邏輯:顯示檢視、獲取郵箱、驗證郵箱、傳送郵件、傳送成功響應、傳送失敗響應等。
那麼為什麼會報錯呢?
因為還沒有配置郵箱!
在.env
中配置一下郵箱:
MAIL_DRIVER=smtp
MAIL_HOST=smtp.163.com
MAIL_PORT=25
MAIL_USERNAME=******
MAIL_PASSWORD=******
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS=******@163.com
MAIL_FROM_NAME=Laravel社群
MAIL_DRIVER
用於配置預設的郵件傳送驅動,此處的最佳選擇是SMTP,其它的要麼收費要麼不能使用。
MAIL_HOST
是郵箱所在主機,對應值是smtp.163.com
。
MAIL_PORT
用於配置郵箱傳送服務埠號,比如一般預設值是25,但如果MAIL_ENCRYPTION
的值是ssl
,該值為465。
MAIL_USERNAME
表示郵箱賬號,比如******@163.com
。
MAIL_PASSWORD
表示上述郵箱登入對應登入密碼。注意使用的是客戶端授權密碼。
MAIL_ENCRYPTION
表示加密型別,可以設定為null表示不使用任何加密,也可以設定為tls
或ssl
。
MAIL_FROM_ADDRESS
表示傳送郵件使用的郵箱。
MAIL_FROM_NAME
表示傳送郵件使用的名稱。
現在再去忘記密碼頁面輸入郵箱點選傳送就不會報錯了。進入自己的郵箱也會收到一封Laravel
內建的漂亮的郵件,開啟郵件,點選Reset Password
,有可能出現一個不是我們預期的頁面,原因也很簡單,因為傳送郵件的時候Laravel
提取了域名拼接成了一個完整的URL
,而域名的配置在app.php
中:
'url' => env('APP_URL', 'http://localhost')
此處使用了env
輔助函式從.env
檔案中讀取APP_URL
配置,如果沒有則使用預設值http://localhost
。那麼我們去.env
中把APP_URL
換成http://laravel.do
(這個是我本地設定的域名)即可。再重新傳送一封郵件,點選Reset Password
就能進入我們預期的頁面了。
5、ResetPasswordController
ResetPasswordController
中還是使用了性狀ResetsPasswords
來負責完成重置密碼的邏輯。Illuminate\Foundation\Auth\ResetsPasswords
的實現邏輯也很簡單,這裡就不做詳細的介紹了,大家可以自行理解。
三、一點想法
對Laravel
使用者認證的淺析到這裡就結束了,我們可以在控制器中覆蓋性狀中的方法來實現定製化開發,比如使用新的試圖,使用新的認證規則等,而這一切的實現也僅僅重新定義一個方法這麼簡單,這就是使用Laravel
的魔力。
在沒有接觸Laravel
之前根本沒用過trait
官方手冊也看明白這是個啥,但就是不知道真實的使用場景,是Laravel
讓我知道它的使用場景。而Laravel
帶給我的也遠不止這些,自從半年前接觸到Laravel
就深深地喜歡上了這款框架,喜歡它並不僅僅因為它很優美,更多的是它的設計思想和高效的開發,高效的開發帶來的好處自不必說,設計思想又可以讓我們學習到很多優秀的思想或者說實現方式,於我們能力的提升是有莫大的好處的,至於執行效率,我先是贊同這句話——程式是寫給人看的,機器只是恰巧能夠執行。隨著硬體和PHP
自身的發展還有這麼多的最佳化手段,我想效能不應該是我們開發者首要考慮的因素。
這一次也透過認真閱讀原始碼,發現閱讀原始碼不但沒那麼可怕,反而會讓我從中不斷找到樂趣,從猜想到猜想被驗證,每次都是一種肯定和成長。個人感覺從一個小功能入手閱讀原始碼可能會比較容易一點。
這是我第一次分享技術的文章,寫了整整半天,現在才體會到分享技術的不易,給樂於分享的技術者們點贊並向他們學習,文章之中如果有不對或不足之處還請大佬們指正,以免誤導他人!
本作品採用《CC 協議》,轉載必須註明作者和本文連結