LaravelDocs(中文)

重設密碼 (Resetting Passwords)

Laravel 提供了方便的密碼重設功能

簡介 (Introduction)

大多數 Web 應用程式都提供讓使用者重設忘記密碼的方法。Laravel 提供了方便的服務來發送密碼重設連結和安全地重設密碼,而不是強迫你為每個應用程式重新手動實作這些功能。

[!NOTE] 想要快速開始嗎?在一個全新的 Laravel 應用程式中安裝 Laravel 應用程式入門套件。Laravel 的入門套件將負責建構整個身分驗證系統,包括重設忘記的密碼。

設定 (Configuration)

應用程式的密碼重設設定檔位於 config/auth.php。請務必查看此檔案中可用的選項。預設情況下,Laravel 設定為使用 database 密碼重設驅動程式。

密碼重設 driver 設定選項定義了密碼重設資料的儲存位置。Laravel 包含兩個驅動程式:

  • database - 密碼重設資料儲存在關聯式資料庫中。
  • cache - 密碼重設資料儲存在你的快取儲存區中。

驅動程式先決條件 (Driver Prerequisites)

Database

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

Cache

還有一個快取驅動程式可用於處理密碼重設,它不需要專用的資料庫資料表。條目由使用者的電子郵件地址作為鍵值,因此請確保你沒有在應用程式的其他地方使用電子郵件地址作為快取鍵值:

'passwords' => [
    'users' => [
        'driver' => 'cache',
        'provider' => 'users',
        'store' => 'passwords', // Optional...
        'expire' => 60,
        'throttle' => 60,
    ],
],

為了防止呼叫 artisan cache:clear 清除你的密碼重設資料,你可以選擇使用 store 設定鍵指定一個單獨的快取儲存區。該值應對應於你的 config/cache.php 設定值中設定的儲存區。

模型準備 (Model Preparation)

在使用 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 來包含實作該介面所需的方法。

設定受信任的主機 (Configuring Trusted Hosts)

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

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

要了解有關此中介軟體方法的更多資訊,請參閱 TrustHosts 中介軟體文件

路由 (Routing)

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

首先,我們將定義請求密碼重設連結所需的路由。首先,我們將定義一個路由,該路由回傳帶有密碼重設連結請求表單的視圖:

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

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

處理表單提交 (Handling the Form Submission)

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

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Password;

Route::post('/forgot-password', function (Request $request) {
    $request->validate(['email' => 'required|email']);

    $status = Password::sendResetLink(
        $request->only('email')
    );

    return $status === Password::ResetLinkSent
        ? back()->with(['status' => __($status)])
        : back()->withErrors(['email' => __($status)]);
})->middleware('guest')->name('password.email');

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

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

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

你可能想知道 Laravel 在呼叫 Password facade 的 sendResetLink 方法時,如何知道如何從應用程式的資料庫中檢索使用者記錄。Laravel 密碼代理利用你的身分驗證系統的「使用者提供者 (user providers)」來檢索資料庫記錄。密碼代理使用的使用者提供者是在 config/auth.php 設定檔的 passwords 設定陣列中設定的。要了解有關編寫自訂使用者提供者的更多資訊,請參閱 身分驗證文件

[!NOTE] 手動實作密碼重設時,你需要自己定義視圖和路由的內容。如果你想要包含所有必要的身分驗證和驗證邏輯的鷹架,請查看 Laravel 應用程式入門套件

重設密碼 (Resetting the Password)

密碼重設表單 (The Password Reset Form)

接下來,我們將定義實際重設密碼所需的路由,一旦使用者點擊發送給他們的密碼重設連結並提供新密碼。首先,讓我們定義將顯示重設密碼表單的路由,該表單在使用者點擊重設密碼連結時顯示。此路由將接收一個 token 參數,我們稍後將使用該參數來驗證密碼重設請求:

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

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

處理表單提交 (Handling the Form Submission)

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

use App\Models\User;
use Illuminate\Auth\Events\PasswordReset;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Password;
use Illuminate\Support\Str;

Route::post('/reset-password', function (Request $request) {
    $request->validate([
        'token' => 'required',
        'email' => 'required|email',
        'password' => 'required|min:8|confirmed',
    ]);

    $status = Password::reset(
        $request->only('email', 'password', 'password_confirmation', 'token'),
        function (User $user, string $password) {
            $user->forceFill([
                'password' => Hash::make($password)
            ])->setRememberToken(Str::random(60));

            $user->save();

            event(new PasswordReset($user));
        }
    );

    return $status === Password::PasswordReset
        ? redirect()->route('login')->with('status', __($status))
        : back()->withErrors(['email' => [__($status)]]);
})->middleware('guest')->name('password.update');

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

如果提供給密碼代理的 token、電子郵件地址和密碼有效,則傳遞給 reset 方法的閉包將被呼叫。在這個閉包中(接收使用者實例和提供給密碼重設表單的純文字密碼),我們可以更新資料庫中使用者的密碼。

reset 方法回傳一個「狀態」slug。可以使用 Laravel 的 在地化 輔助函式翻譯此狀態,以便向使用者顯示有關其請求狀態的友善訊息。密碼重設狀態的翻譯由應用程式的 lang/{lang}/passwords.php 語言檔案決定。狀態 slug 的每個可能值的條目都位於 passwords 語言檔案中。如果你的應用程式不包含 lang 目錄,你可以使用 lang:publish Artisan 指令建立它。

在繼續之前,你可能想知道 Laravel 在呼叫 Password facade 的 reset 方法時,如何知道如何從應用程式的資料庫中檢索使用者記錄。Laravel 密碼代理利用你的身分驗證系統的「使用者提供者 (user providers)」來檢索資料庫記錄。密碼代理使用的使用者提供者是在 config/auth.php 設定檔的 passwords 設定陣列中設定的。要了解有關編寫自訂使用者提供者的更多資訊,請參閱 身分驗證文件

刪除過期的 Token (Deleting Expired Tokens)

如果你使用的是 database 驅動程式,過期的密碼重設 Token 仍將存在於你的資料庫中。但是,你可以使用 auth:clear-resets Artisan 指令輕鬆刪除這些記錄:

php artisan auth:clear-resets

如果你想自動化此過程,請考慮將指令新增至應用程式的 排程器 (scheduler)

use Illuminate\Support\Facades\Schedule;

Schedule::command('auth:clear-resets')->everyFifteenMinutes();

自訂 (Customization)

你可以使用 ResetPassword 通知類別提供的 createUrlUsing 方法來自訂密碼重設連結 URL。此方法接受一個閉包,該閉包接收正在接收通知的使用者實例以及密碼重設連結 Token。通常,你應該從應用程式的 AppServiceProviderboot 方法呼叫此方法:

use App\Models\User;
use Illuminate\Auth\Notifications\ResetPassword;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    ResetPassword::createUrlUsing(function (User $user, string $token) {
        return 'https://example.com/reset-password?token='.$token;
    });
}

自訂重設電子郵件 (Reset Email Customization)

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

use App\Notifications\ResetPasswordNotification;

/**
 * Send a password reset notification to the user.
 *
 * @param  string  $token
 */
public function sendPasswordResetNotification($token): void
{
    $url = 'https://example.com/reset-password?token='.$token;

    $this->notify(new ResetPasswordNotification($url));
}