跳到主要內容

重設密碼

簡介

大多數的網路應用程式都提供使用者重設他們忘記密碼的方式。Laravel 提供了便利的服務來發送密碼重設連結和安全地重設密碼,而不需要您為每個建立的應用程式手動重新實作此功能。

想要快速開始嗎?在一個全新的 Laravel 應用程式中安裝 Laravel 應用程式入門套件。Laravel 的入門套件將會處理您整個身份驗證系統的建構,包含重設忘記密碼的功能。

模型準備

在使用 Laravel 的密碼重設功能之前,您應用程式的 App\Models\User 模型必須使用 Illuminate\Notifications\Notifiable trait。通常,這個 trait 已經包含在新的 Laravel 應用程式所建立的預設 App\Models\User 模型中。

接下來,驗證您的 App\Models\User 模型實作了 Illuminate\Contracts\Auth\CanResetPassword contract。框架包含的 App\Models\User 模型已經實作了這個介面,並且使用 Illuminate\Auth\Passwords\CanResetPassword trait 來包含實作這個介面所需的方法。

資料庫準備

必須建立一個資料表來儲存您應用程式的密碼重設令牌。通常,這包含在 Laravel 預設的 0001_01_01_000000_create_users_table.php 資料庫遷移中。

設定信任的主機

預設情況下,無論 HTTP 請求的 Host header 內容為何,Laravel 都會回應它收到的所有請求。此外,在 Web 請求期間產生應用程式的絕對 URL 時,將會使用 Host header 的值。

通常,您應該設定您的 Web 伺服器,例如 Nginx 或 Apache,使其僅將符合給定主機名稱的請求發送到您的應用程式。但是,如果您無法直接自訂您的 Web 伺服器,並且需要指示 Laravel 僅回應特定的主機名稱,您可以透過在應用程式的 bootstrap/app.php 檔案中使用 trustHosts 中介層方法來做到這一點。當您的應用程式提供密碼重設功能時,這尤其重要。

若要深入了解此中介層方法,請參閱 TrustHosts 中介層文件

路由

為了正確實作允許使用者重設密碼的支援,我們需要定義幾個路由。首先,我們需要一對路由來處理允許使用者透過電子郵件地址請求密碼重設連結。其次,我們需要一對路由來處理使用者在訪問透過電子郵件發送給他們的密碼重設連結並完成密碼重設表單後實際重設密碼。

首先,我們將定義請求密碼重設連結所需的路由。首先,我們將定義一個路由,該路由會返回一個包含密碼重設連結請求表單的視圖

1Route::get('/forgot-password', function () {
2 return view('auth.forgot-password');
3})->middleware('guest')->name('password.request');

此路由返回的視圖應該有一個表單,其中包含一個 email 欄位,這將允許使用者為給定的電子郵件地址請求密碼重設連結。

接下來,我們將定義一個路由,該路由處理來自「忘記密碼」視圖的表單提交請求。此路由將負責驗證電子郵件地址並將密碼重設請求發送到對應的使用者

1use Illuminate\Http\Request;
2use Illuminate\Support\Facades\Password;
3 
4Route::post('/forgot-password', function (Request $request) {
5 $request->validate(['email' => 'required|email']);
6 
7 $status = Password::sendResetLink(
8 $request->only('email')
9 );
10 
11 return $status === Password::ResetLinkSent
12 ? back()->with(['status' => __($status)])
13 : back()->withErrors(['email' => __($status)]);
14})->middleware('guest')->name('password.email');

在繼續之前,讓我們更詳細地檢查這個路由。首先,請求的 email 屬性會被驗證。接下來,我們將使用 Laravel 內建的「密碼代理」(透過 Password facade)來發送密碼重設連結給使用者。密碼代理將負責透過給定的欄位(在此案例中為電子郵件地址)檢索使用者,並透過 Laravel 內建的 通知系統 發送使用者密碼重設連結。

sendResetLink 方法會返回一個「狀態」slug。這個狀態可以使用 Laravel 的 本地化 helpers 進行翻譯,以便向使用者顯示有關其請求狀態的使用者友善訊息。密碼重設狀態的翻譯取決於應用程式的 lang/{lang}/passwords.php 語言檔案。每個可能的狀態 slug 值在 passwords 語言檔案中都有一個條目。

預設情況下,Laravel 應用程式骨架不包含 lang 目錄。如果您想要自訂 Laravel 的語言檔案,您可以透過 lang:publish Artisan 命令發布它們。

您可能想知道當呼叫 Password facade 的 sendResetLink 方法時,Laravel 如何知道從應用程式的資料庫中檢索使用者記錄。Laravel 密碼代理利用您的身份驗證系統的「使用者提供器」來檢索資料庫記錄。密碼代理使用的使用者提供器在 config/auth.php 設定檔案的 passwords 設定陣列中設定。若要深入了解如何編寫自訂使用者提供器,請參閱 身份驗證文件

當手動實作密碼重設時,您需要自行定義視圖和路由的內容。如果您想要包含所有必要的身份驗證和驗證邏輯的骨架,請查看 Laravel 應用程式入門套件

重設密碼

密碼重設表單

接下來,我們將定義必要的路由,以便在使用者點擊透過電子郵件發送給他們的密碼重設連結並提供新密碼後實際重設密碼。首先,讓我們定義將顯示密碼重設表單的路由,該表單在使用者點擊密碼重設連結時顯示。此路由將接收一個 token 參數,我們稍後將使用它來驗證密碼重設請求

1Route::get('/reset-password/{token}', function (string $token) {
2 return view('auth.reset-password', ['token' => $token]);
3})->middleware('guest')->name('password.reset');

此路由返回的視圖應該顯示一個表單,其中包含一個 email 欄位、一個 password 欄位、一個 password_confirmation 欄位和一個隱藏的 token 欄位,該欄位應包含我們路由接收到的秘密 $token 的值。

處理表單提交

當然,我們需要定義一個路由來實際處理密碼重設表單提交。此路由將負責驗證傳入的請求並更新資料庫中使用者的密碼

1use App\Models\User;
2use Illuminate\Auth\Events\PasswordReset;
3use Illuminate\Http\Request;
4use Illuminate\Support\Facades\Hash;
5use Illuminate\Support\Facades\Password;
6use Illuminate\Support\Str;
7 
8Route::post('/reset-password', function (Request $request) {
9 $request->validate([
10 'token' => 'required',
11 'email' => 'required|email',
12 'password' => 'required|min:8|confirmed',
13 ]);
14 
15 $status = Password::reset(
16 $request->only('email', 'password', 'password_confirmation', 'token'),
17 function (User $user, string $password) {
18 $user->forceFill([
19 'password' => Hash::make($password)
20 ])->setRememberToken(Str::random(60));
21 
22 $user->save();
23 
24 event(new PasswordReset($user));
25 }
26 );
27 
28 return $status === Password::PasswordReset
29 ? redirect()->route('login')->with('status', __($status))
30 : back()->withErrors(['email' => [__($status)]]);
31})->middleware('guest')->name('password.update');

在繼續之前,讓我們更詳細地檢查這個路由。首先,請求的 tokenemailpassword 屬性會被驗證。接下來,我們將使用 Laravel 內建的「密碼代理」(透過 Password facade)來驗證密碼重設請求憑證。

如果提供給密碼代理的令牌、電子郵件地址和密碼有效,則會調用傳遞給 reset 方法的 closure。在這個 closure 中,它接收使用者實例和提供給密碼重設表單的純文字密碼,我們可以更新資料庫中使用者的密碼。

reset 方法會返回一個「狀態」slug。這個狀態可以使用 Laravel 的 本地化 helpers 進行翻譯,以便向使用者顯示有關其請求狀態的使用者友善訊息。密碼重設狀態的翻譯取決於應用程式的 lang/{lang}/passwords.php 語言檔案。每個可能的狀態 slug 值在 passwords 語言檔案中都有一個條目。如果您的應用程式不包含 lang 目錄,您可以使用 lang:publish Artisan 命令建立它。

在繼續之前,您可能想知道當呼叫 Password facade 的 reset 方法時,Laravel 如何知道從應用程式的資料庫中檢索使用者記錄。Laravel 密碼代理利用您的身份驗證系統的「使用者提供器」來檢索資料庫記錄。密碼代理使用的使用者提供器在 config/auth.php 設定檔案的 passwords 設定陣列中設定。若要深入了解如何編寫自訂使用者提供器,請參閱 身份驗證文件

刪除過期的令牌

過期的密碼重設令牌仍然會存在於您的資料庫中。但是,您可以使用 auth:clear-resets Artisan 命令輕鬆刪除這些記錄

1php artisan auth:clear-resets

如果您想自動化此過程,請考慮將此命令新增至應用程式的 scheduler

1use Illuminate\Support\Facades\Schedule;
2 
3Schedule::command('auth:clear-resets')->everyFifteenMinutes();

自訂

您可以使用 ResetPassword 通知類別提供的 createUrlUsing 方法自訂密碼重設連結 URL。此方法接受一個 closure,該 closure 接收正在接收通知的使用者實例以及密碼重設連結令牌。通常,您應該從 App\Providers\AppServiceProvider 服務提供者的 boot 方法中呼叫此方法

1use App\Models\User;
2use Illuminate\Auth\Notifications\ResetPassword;
3 
4/**
5 * Bootstrap any application services.
6 */
7public function boot(): void
8{
9 ResetPassword::createUrlUsing(function (User $user, string $token) {
10 return 'https://example.com/reset-password?token='.$token;
11 });
12}

重設電子郵件自訂

您可以輕鬆修改用於向使用者發送密碼重設連結的通知類別。要開始使用,請覆寫 App\Models\User 模型上的 sendPasswordResetNotification 方法。在這個方法中,您可以使用您自己建立的任何 通知類別 發送通知。密碼重設 $token 是此方法接收的第一個參數。您可以使用這個 $token 來建立您選擇的密碼重設 URL,並將通知發送給使用者

1use App\Notifications\ResetPasswordNotification;
2 
3/**
4 * Send a password reset notification to the user.
5 *
6 * @param string $token
7 */
8public function sendPasswordResetNotification($token): void
9{
10 $url = 'https://example.com/reset-password?token='.$token;
11 
12 $this->notify(new ResetPasswordNotification($url));
13}