認證
📝 此頁面為 Laravel 官方文檔的繁體中文翻譯。查看原始英文版本
認證
簡介
許多 Web 應用程式為其使用者提供了一種透過應用程式進行認證和「登入」的方式。在 Web 應用程式中實現此功能可能是一個複雜且潛在有風險的過程。為此,Laravel 努力為您提供快速、安全且輕鬆地實現認證所需的工具。
在其核心中,Laravel 的認證設施由「守衛」和「提供者」組成。守衛定義了如何對每個請求進行使用者認證。例如,Laravel 附帶一個 session 守衛,它使用會話儲存和 Cookie 維護狀態。
提供者定義了如何從您的持久儲存中檢索使用者。Laravel 附帶對使用 Eloquent 和資料庫查詢建構器檢索使用者的支援。但是,您可以根據應用程式的需要自由定義額外的提供者。
您應用程式的認證配置檔案位於 config/auth.php。此檔案包含幾個有詳細文檔的選項,用於調整 Laravel 認證服務的行為。
[!NOTE] 不應將守衛和提供者與「角色」和「權限」混淆。要了解更多關於透過權限授權使用者操作的資訊,請參閱授權文檔。
入門套件
想要快速開始嗎?在全新的 Laravel 應用程式中安裝 Laravel 應用程式入門套件。遷移資料庫後,將瀏覽器導航到 /register 或分配給您應用程式的任何其他 URL。入門套件將負責搭建整個認證系統!
即使您選擇不在最終的 Laravel 應用程式中使用入門套件,安裝入門套件也可以是一個絕佳的機會,讓您學習如何在實際的 Laravel 專案中實現所有 Laravel 的認證功能。由於 Laravel 入門套件包含認證控制器、路由和視圖,您可以檢查這些檔案中的代碼來學習如何實現 Laravel 的認證功能。
資料庫考量
預設情況下,Laravel 在 app/Models 目錄中包含一個 App\Models\User Eloquent 模型。此模型可用於預設的 Eloquent 認證驅動程式。
如果您的應用程式不使用 Eloquent,您可以使用 database 認證提供者,它使用 Laravel 查詢建構器。如果您的應用程式使用 MongoDB,請查看 MongoDB 的官方 Laravel 使用者認證文檔。
建立 App\Models\User 模型的資料庫架構時,請確保密碼欄位至少有 60 個字元長度。當然,新的 Laravel 應用程式中附帶的 users 表遷移已經建立了一個超過此長度的欄位。
此外,您應該驗證您的 users(或等效)表包含一個可空的、100 個字元的字串 remember_token 欄位。此欄位將用於儲存選擇在登入應用程式時「記住我」選項的使用者的令牌。同樣,新的 Laravel 應用程式中附帶的預設 users 表遷移已經包含此欄位。
生態系統概述
Laravel 提供了多個與認證相關的套件。在繼續之前,我們將回顧 Laravel 中的一般認證生態系統並討論每個套件的預期用途。
首先,考慮認證如何運作。使用 Web 瀏覽器時,使用者將透過登入表單提供其使用者名稱和密碼。如果這些憑據正確,應用程式將在使用者的會話中儲存有關已認證使用者的資訊。發送到瀏覽器的 Cookie 包含會話 ID,以便對應用程式的後續請求可以將使用者與正確的會話關聯。收到會話 Cookie 後,應用程式將根據會話 ID 檢索會話資料,注意到認證資訊已儲存在會話中,並將使用者視為「已認證」。
當遠端服務需要認證以存取 API 時,Cookie 通常不用於認證,因為沒有 Web 瀏覽器。相反,遠端服務在每個請求上向 API 發送 API 令牌。應用程式可以根據有效 API 令牌表驗證傳入的令牌,並將請求「認證」為由與該 API 令牌關聯的使用者執行。
Laravel 的內建瀏覽器認證服務
Laravel 包含內建的認證和會話服務,通常透過 Auth 和 Session 外觀存取。這些功能為從 Web 瀏覽器發起的請求提供基於 Cookie 的認證。它們提供了允許您驗證使用者憑據並認證使用者的方法。此外,這些服務將自動在使用者的會話中儲存適當的認證資料,並發送使用者的會話 Cookie。有關如何使用這些服務的討論包含在此文檔中。
應用程式入門套件
正如本文檔中所討論的,您可以與這些認證服務互動以建立您自己的認證層。但是,為了幫助您更快地開始,我們發布了免費入門套件,提供了整個認證層的強大、現代化的腳手架。
Laravel 的 API 認證服務
Laravel 提供了兩個可選套件來協助您管理 API 令牌並驗證使用 API 令牌發出的請求:Passport 和 Sanctum。請注意,這些庫和 Laravel 的內建基於 Cookie 的認證庫並不是互斥的。這些庫主要關注 API 令牌認證,而內建認證服務則關注基於 Cookie 的瀏覽器認證。許多應用程式將同時使用 Laravel 的內建基於 Cookie 的認證服務和 Laravel 的 API 認證套件之一。
Passport
Passport 是一個 OAuth2 認證提供者,提供了多種 OAuth2「授權類型」,允許您發行各種類型的令牌。一般來說,這是一個強大且複雜的 API 認證套件。但是,大多數應用程式不需要 OAuth2 規範提供的複雜功能,這可能對使用者和開發者來說都很困惑。此外,開發者歷史上對於如何使用 OAuth2 認證提供者(如 Passport)來認證 SPA 應用程式或行動應用程式感到困惑。
Sanctum
為了回應 OAuth2 的複雜性和開發者的困惑,我們著手建立一個更簡單、更精簡的認證套件,它可以處理來自 Web 瀏覽器的第一方 Web 請求和透過令牌的 API 請求。隨著 Laravel Sanctum 的發布,這個目標得以實現,它應該被認為是將提供第一方 Web UI 和 API 的應用程式、或將由與後端 Laravel 應用程式分開存在的單頁應用程式(SPA)驅動的應用程式、或提供行動客戶端的應用程式的首選和推薦認證套件。
Laravel Sanctum 是一個混合式 Web / API 認證套件,可以管理您應用程式的整個認證過程。這是可能的,因為當基於 Sanctum 的應用程式收到請求時,Sanctum 將首先判斷該請求是否包含引用已認證會話的會話 Cookie。Sanctum 透過呼叫我們之前討論過的 Laravel 內建認證服務來實現這一點。如果請求不是透過會話 Cookie 認證的,Sanctum 將檢查請求是否有 API 令牌。如果存在 API 令牌,Sanctum 將使用該令牌認證請求。要了解更多關於此過程的資訊,請查閱 Sanctum 的「運作方式」文檔。
概述和選擇您的技術棧
總之,如果您的應用程式將使用瀏覽器存取,並且您正在建立一個單體 Laravel 應用程式,則您的應用程式將使用 Laravel 的內建認證服務。
接下來,如果您的應用程式提供一個將被第三方使用的 API,您將在 Passport 和 Sanctum 之間做出選擇,為您的應用程式提供 API 令牌認證。一般來說,應優先選擇 Sanctum,因為它是 API 認證、SPA 認證和行動認證的簡單、完整的解決方案,包括對「範圍」或「能力」的支援。
如果您正在建立一個將由 Laravel 後端驅動的單頁應用程式(SPA),則應該使用 Laravel Sanctum。使用 Sanctum 時,您需要手動實現自己的後端認證路由或利用 Laravel Fortify 作為無頭認證後端服務,它為註冊、密碼重設、電子郵件驗證等功能提供路由和控制器。
當您的應用程式絕對需要 OAuth2 規範提供的所有功能時,可以選擇 Passport。
而且,如果您想要快速開始,我們很高興推薦我們的應用程式入門套件作為快速開始新 Laravel 應用程式的一種方式,該應用程式已經使用了我們首選的 Laravel 內建認證服務認證技術棧。
認證快速入門
[!WARNING] 本文檔的此部分討論了透過 Laravel 應用程式入門套件認證使用者,其中包括 UI 腳手架以幫助您快速開始。如果您希望直接與 Laravel 的認證系統整合,請查看有關手動認證使用者的文檔。
安裝入門套件
首先,您應該安裝 Laravel 應用程式入門套件。我們的入門套件提供了美麗設計的起點,用於將認證整合到您的全新 Laravel 應用程式中。
檢索已認證的使用者
從入門套件建立應用程式並允許使用者註冊並使用您的應用程式進行認證後,您通常需要與當前已認證的使用者互動。處理傳入請求時,您可以透過 Auth 外觀的 user 方法存取已認證的使用者:
use Illuminate\Support\Facades\Auth;
// 檢索當前已認證的使用者...
$user = Auth::user();
// 檢索當前已認證的使用者的 ID...
$id = Auth::id();
或者,一旦使用者被認證,您可以透過 Illuminate\Http\Request 實例存取已認證的使用者。請記住,鍵入提示的類別將自動注入到您的控制器方法中。透過鍵入提示 Illuminate\Http\Request 物件,您可以透過請求的 user 方法從應用程式中的任何控制器方法輕鬆存取已認證的使用者:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
class FlightController extends Controller
{
/**
* 更新現有航班的航班資訊。
*/
public function update(Request $request): RedirectResponse
{
$user = $request->user();
// ...
return redirect('/flights');
}
}
判斷當前使用者是否已認證
要判斷發出傳入 HTTP 請求的使用者是否已認證,您可以使用 Auth 外觀上的 check 方法。如果使用者已認證,此方法將傳回 true:
use Illuminate\Support\Facades\Auth;
if (Auth::check()) {
// 使用者已登入...
}
[!NOTE] 即使可以使用
check方法判斷使用者是否已認證,您通常會使用中介層來驗證使用者是否已認證,然後再允許使用者存取某些路由/控制器。要了解更多關於此的資訊,請查看有關保護路由的文檔。
保護路由
路由中介層可用於僅允許已認證的使用者存取給定的路由。Laravel 附帶一個 auth 中介層,它是 Illuminate\Auth\Middleware\Authenticate 類別的中介層別名。由於此中介層在內部已被 Laravel 別名化,您只需將中介層附加到路由定義:
Route::get('/flights', function () {
// 只有已認證的使用者可以存取此路由...
})->middleware('auth');
重定向未認證的使用者
當 auth 中介層偵測到未認證的使用者時,它將把使用者重定向到 login命名路由。您可以使用應用程式 bootstrap/app.php 檔案中的 redirectGuestsTo 方法來修改此行為:
use Illuminate\Http\Request;
->withMiddleware(function (Middleware $middleware): void {
$middleware->redirectGuestsTo('/login');
// 使用閉包...
$middleware->redirectGuestsTo(fn (Request $request) => route('login'));
})
重定向已認證的使用者
當 guest 中介層偵測到已認證的使用者時,它將把使用者重定向到 dashboard 或 home 命名路由。您可以使用應用程式 bootstrap/app.php 檔案中的 redirectUsersTo 方法來修改此行為:
use Illuminate\Http\Request;
->withMiddleware(function (Middleware $middleware): void {
$middleware->redirectUsersTo('/panel');
// 使用閉包...
$middleware->redirectUsersTo(fn (Request $request) => route('panel'));
})
指定守衛
將 auth 中介層附加到路由時,您還可以指定應使用哪個「守衛」來認證使用者。指定的守衛應對應於 auth.php 配置檔案的 guards 陣列中的鍵之一:
Route::get('/flights', function () {
// 只有已認證的使用者可以存取此路由...
})->middleware('auth:admin');
登入節流
如果您使用我們的應用程式入門套件之一,速率限制將自動應用於登入嘗試。預設情況下,如果使用者在幾次嘗試後未能提供正確的憑據,則該使用者將無法在一分鐘內登入。節流是唯一的使用者使用者名稱/電子郵件地址及其 IP 位址。
[!NOTE] 如果您希望對應用程式中的其他路由進行速率限制,請查看速率限制文檔。
手動認證使用者
您不必使用 Laravel 應用程式入門套件中包含的認證腳手架。如果您選擇不使用此腳手架,則需要直接使用 Laravel 認證類別來管理使用者認證。別擔心,這很簡單!
我們將透過 Auth外觀存取 Laravel 的認證服務,因此我們需要確保在類別頂部匯入 Auth 外觀。接下來,讓我們看看 attempt 方法。attempt 方法通常用於處理來自應用程式「登入」表單的認證嘗試。如果認證成功,您應該重新生成使用者的會話以防止會話固定:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Auth;
class LoginController extends Controller
{
/**
* 處理認證嘗試。
*/
public function authenticate(Request $request): RedirectResponse
{
$credentials = $request->validate([
'email' => ['required', 'email'],
'password' => ['required'],
]);
if (Auth::attempt($credentials)) {
$request->session()->regenerate();
return redirect()->intended('dashboard');
}
return back()->withErrors([
'email' => '提供的憑據與我們的記錄不匹配。',
])->onlyInput('email');
}
}
attempt 方法接受一個鍵/值對陣列作為其第一個參數。陣列中的值將用於在您的資料庫表中尋找使用者。因此,在上面的範例中,將根據 email 欄位的值檢索使用者。如果找到使用者,資料庫中儲存的雜湊密碼將與透過陣列傳遞給方法的 password 值進行比較。您不應雜湊傳入請求的 password 值,因為框架會在將其與資料庫中的雜湊密碼進行比較之前自動雜湊該值。如果兩個雜湊密碼匹配,將為使用者開始已認證的會話。
請記住,Laravel 的認證服務將根據認證守衛的「提供者」配置從您的資料庫中檢索使用者。在預設的 config/auth.php 配置檔案中,指定了 Eloquent 使用者提供者,並指示它在檢索使用者時使用 App\Models\User 模型。您可以根據應用程式的需要在配置檔案中更改這些值。
attempt 方法在認證成功時將傳回 true。否則,將傳回 false。
Laravel 的重定導器提供的 intended 方法將把使用者重定向到他們在被認證中間層攔截之前嘗試存取的 URL。可以向此方法提供一個後備 URI,以防目標目的地不可用。
指定額外條件
如果您願意,除了使用者的電子郵件和密碼外,您還可以向認證查詢添加額外的查詢條件。為此,我們只需將查詢條件添加到傳遞給 attempt 方法的陣列中。例如,我們可以驗證使用者是否被標記為「活躍」:
if (Auth::attempt(['email' => $email, 'password' => $password, 'active' => 1])) {
// 認證成功...
}
對於複雜的查詢條件,您可以在憑據陣列中提供一個閉包。此閉包將與查詢實例一起被呼叫,允許您根據應用程式的需要自訂查詢:
use Illuminate\Database\Eloquent\Builder;
if (Auth::attempt([
'email' => $email,
'password' => $password,
fn (Builder $query) => $query->has('activeSubscription'),
])) {
// 認證成功...
}
[!WARNING] 在這些範例中,
attemptWhen 方法接受一個閉包作為其第二個參數,可用於在實際認證使用者之前對潛在使用者進行更廣泛的檢查。閉包接收潛在使用者,並應傳回 true 或 false 以指示是否可以認證該使用者:
if (Auth::attemptWhen([
'email' => $email,
'password' => $password,
], function (User $user) {
return $user->isNotBanned();
})) {
// 認證成功...
}
存取特定守衛實例
透過 Auth 外觀的 guard 方法,您可以指定在認證使用者時希望使用哪個守衛實例。這允許您使用完全不同的可認證模型或使用者表來管理應用程式不同部分的認證。
傳遞給 guard 方法的守衛名稱應對應於 auth.php 配置檔案中配置的守衛之一:
if (Auth::guard('admin')->attempt($credentials)) {
// ...
}
記住使用者
許多 Web 應用程式在其登入表單上提供一個「記住我」複選框。如果您希望在應用程式中提供「記住我」功能,可以將布林值作為 attempt 方法的第二個參數傳遞。
當此值為 true 時,Laravel 將無限期地保持使用者已認證,或者直到他們手動登出。您的 users 表必須包含字串 remember_token 欄位,它將用於儲存「記住我」令牌。新的 Laravel 應用程式中附帶的 users 表遷移已經包含此欄位:
use Illuminate\Support\Facades\Auth;
if (Auth::attempt(['email' => $email, 'password' => $password], $remember)) {
// 使用者正在被記住...
}
如果您的應用程式提供「記住我」功能,您可以使用 viaRemember 方法判斷當前已認證的使用者是否使用「記住我」Cookie 進行認證:
use Illuminate\Support\Facades\Auth;
if (Auth::viaRemember()) {
// ...
}
其他認證方法
認證使用者實例
如果您需要將現有使用者實例設定為當前已認證的使用者,可以將使用者實例傳遞給 Auth 外觀的 login 方法。給定的使用者實例必須是 Illuminate\Contracts\Auth\Authenticatable合約的實現。Laravel 附帶的 App\Models\User 模型已經實現了此介面。當您已經有一個有效的使用者實例時,此認證方法很有用,例如在使用者註冊您的應用程式後立即:
use Illuminate\Support\Facades\Auth;
Auth::login($user);
您可以將布林值作為 login 方法的第二個參數傳遞。此值指示是否希望為已認證的會話提供「記住我」功能。請記住,這意味著會話將被無限期認證,或者直到使用者手動從應用程式登出:
Auth::login($user, $remember = true);
如果需要,您可以在呼叫 login 方法之前指定認證守衛:
Auth::guard('admin')->login($user);
透過 ID 認證使用者
要使用使用者資料庫記錄的主鍵認證使用者,您可以使用 loginUsingId 方法。此方法接受您希望認證的使用者的主鍵:
Auth::loginUsingId(1);
您可以將布林值傳遞給 loginUsingId 方法的 remember 參數。此值指示是否希望為已認證的會話提供「記住我」功能。請記住,這意味著會話將被無限期認證,或者直到使用者手動從應用程式登出:
Auth::loginUsingId(1, remember: true);
一次性認證使用者
您可以使用 once 方法在單個請求中使用應用程式認證使用者。呼叫此方法時不會使用會話或 Cookie,並且不會派發 Login 事件:
if (Auth::once($credentials)) {
// ...
}
HTTP 基本認證
HTTP 基本認證提供了一種快速認證應用程式使用者的方法,無需設置專用的「登入」頁面。首先,將 auth.basic中介層附加到路由。auth.basic 中介層包含在 Laravel 框架中,因此您不需要定義它:
Route::get('/profile', function () {
// 只有已認證的使用者可以存取此路由...
})->middleware('auth.basic');
一旦中介層被附加到路由,您將在瀏覽器中存取路由時自動收到憑據提示。預設情況下,auth.basic 中介層將假設 users 資料庫表上的 email 欄位是使用者的「使用者名稱」。
關於 FastCGI 的說明
如果您使用 PHP FastCGI 和 Apache 來提供 Laravel 應用程式,HTTP 基本認證可能無法正常運作。為了修正這些問題,可以將以下行添加到您應用程式的 .htaccess 檔案中:
RewriteCond %{HTTP:Authorization} ^(.+)$
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
無狀態 HTTP 基本認證
您還可以在不在會話中設定使用者標識符 Cookie 的情況下使用 HTTP 基本認證。如果您選擇使用 HTTP 認證來認證對應用程式 API 的請求,這主要很有幫助。為此,定義一個呼叫 onceBasic 方法的中介層。如果 onceBasic 方法沒有傳回回應,則請求可以進一步傳遞到應用程式:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Symfony\Component\HttpFoundation\Response;
class AuthenticateOnceWithBasicAuth
{
/**
* 處理傳入的請求。
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
return Auth::onceBasic() ?: $next($request);
}
}
接下來,將中介層附加到路由:
Route::get('/api/user', function () {
// 只有已認證的使用者可以存取此路由...
})->middleware(AuthenticateOnceWithBasicAuth::class);
登出
要手動將使用者從應用程式中登出,您可以使用 Auth 外觀提供的 logout 方法。這將從使用者的會話中移除認證資訊,以便後續請求不會被認證。
除了呼叫 logout 方法外,建議您使使用者的會話失效並重新生成其 CSRF 令牌。將使用者登出後,您通常會將使用者重定向到應用程式的根目錄:
use Illuminate\Http\Request;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Auth;
/**
* 將使用者從應用程式中登出。
*/
public function logout(Request $request): RedirectResponse
{
Auth::logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect('/');
}
使其他設備上的會話失效
Laravel 還提供了一種機制,用於使使用者在其他設備上處於活動狀態的會話失效並「登出」,而不會使其當前設備上的會話失效。此功能通常在使用者更改或更新其密碼時使用,您希望使其他設備上的會話失效,同時保持當前設備已認證。
開始之前,您應確保 Illuminate\Session\Middleware\AuthenticateSession 中介層包含在應接收會話認證的路由上。通常,您應該將此中介層放在路由群組定義上,以便它可以應用於應用程式的大多數路由。預設情況下,AuthenticateSession 中介層可以使用 auth.session中介層別名附加到路由:
Route::middleware(['auth', 'auth.session'])->group(function () {
Route::get('/', function () {
// ...
});
});
然後,您可以使用 Auth 外觀提供的 logoutOtherDevices 方法。此方法需要使用者確認其當前密碼,您的應用程式應透過輸入表單接受該密碼:
use Illuminate\Support\Facades\Auth;
Auth::logoutOtherDevices($currentPassword);
當呼叫 logoutOtherDevices 方法時,使用者的其他會話將完全失效,這意味著他們將從之前認證的所有守衛中「登出」。
密碼確認
在建立應用程式時,您可能偶爾會有需要使用者在執行操作或將使用者重定向到應用程式的敏感區域之前確認其密碼的操作。Laravel 包含內建的中介層來使此過程變得輕而鬆。實現此功能將需要您定義兩個路由:一個路由顯示要求使用者確認其密碼的視圖,另一個路由確認密碼有效並將使用者重定向到其預期目的地。
[!NOTE] 以下文檔討論了如何直接與 Laravel 的密碼確認功能整合;但是,如果您想要更快地開始,Laravel 應用程式入門套件包含對此功能的支援!
配置
確認密碼後,在三小時內不會再次要求使用者確認其密碼。但是,您可以透過更改應用程式 config/auth.php 配置檔案中的 password_timeout 配置值來配置重新提示使用者輸入密碼之前的時間長度。
路由
密碼確認表單
首先,我們將定義一個路由來顯示要求使用者確認其密碼的視圖:
Route::get('/confirm-password', function () {
return view('auth.confirm-password');
})->middleware('auth')->name('password.confirm');
正如您所預期的,此路由傳回的視圖應包含一個帶有 password 欄位的表單。此外,請隨意在視圖中包含說明使用者正在進入應用程式的受保護區域並必須確認其密碼的文字。
確認密碼
接下來,我們將定義一個路由來處理來自「確認密碼」視圖的表單請求。此路由將負責驗證密碼並將使用者重定向到其預期目的地:
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
Route::post('/confirm-password', function (Request $request) {
if (! Hash::check($request->password, $request->user()->password)) {
return back()->withErrors([
'password' => ['提供的密碼與我們的記錄不匹配。']
]);
}
$request->session()->passwordConfirmed();
return redirect()->intended();
})->middleware(['auth', 'throttle:6,1']);
在繼續之前,讓我們更詳細地檢查此路由。首先,判斷請求的 password 欄位是否實際匹配已認證使用者的密碼。如果密碼有效,我們需要通知 Laravel 的會話使用者已確認其密碼。passwordConfirmed 方法將在使用者的會話中設定一個時間戳,Laravel 可以使用該時間戳來判斷使用者最後一次確認密碼的時間。最後,我們可以將使用者重定向到其預期目的地。
保護路由
您應該確保任何執行需要最近密碼確認的操作的路由都被分配 password.confirm 中介層。此中介層包含在 Laravel 的預設安裝中,它將自動將使用者的預期目的地儲存在會話中,以便使用者可以在確認密碼後被重定向到該位置。將使用者的預期目的地儲存在會話中後,中介層將把使用者重定向到 password.confirm命名路由:
Route::get('/settings', function () {
// ...
})->middleware(['password.confirm']);
Route::post('/settings', function () {
// ...
})->middleware(['password.confirm']);
添加自訂守衛
您可以使用 Auth 外觀上的 extend 方法定義自己的認證守衛。您應該在服務提供者中放置對 extend 方法的呼叫。由於 Laravel 已經附帶 AppServiceProvider,我們可以將代碼放在該提供者中:
<?php
namespace App\Providers;
use App\Services\Auth\JwtGuard;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
// ...
/**
* 啟動任何應用程式服務。
*/
public function boot(): void
{
Auth::extend('jwt', function (Application $app, string $name, array $config) {
// 返回 Illuminate\Contracts\Auth\Guard 的實現...
return new JwtGuard(Auth::createUserProvider($config['provider']));
});
}
}
正如您在上面的範例中所見,傳遞給 extend 方法的回呼應返回 Illuminate\Contracts\Auth\Guard 的實現。此介面包含您需要實現以定義自訂守衛的一些方法。定義自訂守衛後,您可以在 auth.php 配置檔案的 guards 配置中引用該守衛:
'guards' => [
'api' => [
'driver' => 'jwt',
'provider' => 'users',
],
],
閉包請求守衛
實現基於 HTTP 請求的自訂認證系統的最簡單方法是使用 Auth::viaRequest 方法。此方法允許您使用單個閉包快速定義認證過程。
首先,在應用程式的 AppServiceProvider 的 boot 方法中呼叫 Auth::viaRequest 方法。viaRequest 方法接受一個認證驅動程式名稱作為其第一個參數。此名稱可以是描述您的自訂守衛的任何字串。傳遞給方法的第二個參數應是一個閉包,該閉包接收傳入的 HTTP 請求並返回一個使用者實例,或者如果認證失敗則返回 null:
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
/**
* 啟動任何應用程式服務。
*/
public function boot(): void
{
Auth::viaRequest('custom-token', function (Request $request) {
return User::where('token', (string) $request->token)->first();
});
}
定義自訂認證驅動程式後,您可以在 auth.php 配置檔案的 guards 配置中將其配置為驅動程式:
'guards' => [
'api' => [
'driver' => 'custom-token',
],
],
最後,您可以在將認證中介層分配給路由時引用該守衛:
Route::middleware('auth:api')->group(function () {
// ...
});
添加自訂使用者提供者
如果您不使用傳統的關聯資料庫來儲存使用者,則需要使用您自己的認證使用者提供者來擴展 Laravel。我們將使用 Auth 外觀上的 provider 方法來定義自訂使用者提供者。使用者提供者解析器應返回 Illuminate\Contracts\Auth\UserProvider 的實現:
<?php
namespace App\Providers;
use App\Extensions\MongoUserProvider;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
// ...
/**
* 啟動任何應用程式服務。
*/
public function boot(): void
{
Auth::provider('mongo', function (Application $app, array $config) {
// 返回 Illuminate\Contracts\Auth\UserProvider 的實現...
return new MongoUserProvider($app->make('mongo.connection'));
});
}
}
使用 provider 方法註冊提供者後,您可以在 auth.php 配置檔案中切換到新的使用者提供者。首先,定義一個使用新驅動程式的 provider:
'providers' => [
'users' => [
'driver' => 'mongo',
],
],
最後,您可以在 guards 配置中引用此提供者:
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
],
使用者提供者合約
Illuminate\Contracts\Auth\UserProvider 的實現負責從持久儲存系統(如 MySQL、MongoDB 等)中獲取 Illuminate\Contracts\Auth\Authenticatable 的實現。這兩個介面允許 Laravel 認證機制繼續運作,無論使用者資料如何儲存或使用什麼類型的類別來表示已認證的使用者。
讓我們來看看 Illuminate\Contracts\Auth\UserProvider 合約:
<?php
namespace Illuminate\Contracts\Auth;
interface UserProvider
{
public function retrieveById($identifier);
public function retrieveByToken($identifier, $token);
public function updateRememberToken(Authenticatable $user, $token);
public function retrieveByCredentials(array $credentials);
public function validateCredentials(Authenticatable $user, array $credentials);
public function rehashPasswordIfRequired(Authenticatable $user, array $credentials, bool $force = false);
}
retrieveById 函數通常接收一個代表使用者的鍵,例如來自 MySQL 資料庫的自動遞增 ID。應檢索並返回與 ID 匹配的 Authenticatable 實現。
retrieveByToken 函數透過使用者的唯一 $identifier 和「記住我」$token 檢索使用者,通常儲存在資料庫欄位(如 remember_token)中。與上一個方法一樣,此方法應返回具有匹配令牌值的 Authenticatable 實現。
updateRememberToken 方法使用新 $token 更新 $user 實例的 remember_token。在成功的「記住我」認證嘗試或使用者登出時,會向使用者分配一個新的令牌。
retrieveByCredentials 方法接收在嘗試使用應用程式進行認證時傳遞給 Auth::attempt 方法的憑據陣列。然後,該方法應「查詢」底層持久儲存以獲取與這些憑據匹配的使用者。通常,此方法將運行一個帶有「where」條件的查詢,該條件搜索具有與 $credentials['username'] 的值匹配的「使用者名稱」的使用者記錄。此方法應返回 Authenticatable 的實現。此方法不應嘗試進行任何密碼驗證或認證。
validateCredentials 方法應將給定的 $user 與 $credentials 進行比較以認證使用者。例如,此方法通常使用 Hash::check 方法來比較 $user->getAuthPassword() 的值與 $credentials['password'] 的值。此方法應傳回 true 或 false,指示密碼是否有效。
rehashPasswordIfRequired 方法應在需要且支援時重新雜湊給定 $user 的密碼。例如,此方法通常使用 Hash::needsRehash 方法來判斷 $credentials['password'] 的值是否需要重新雜湊。如果密碼需要重新雜湊,該方法應使用 Hash::make 方法來重新雜湊密碼並更新底層持久儲存中的使用者記錄。
可認證合約
現在我們已經探索了 UserProvider 上的每個方法,讓我們來看看 Authenticatable 合約。請記住,使用者提供者應從 retrieveById、retrieveByToken 和 retrieveByCredentials 方法返回此介面的實現:
<?php
namespace Illuminate\Contracts\Auth;
interface Authenticatable
{
public function getAuthIdentifierName();
public function getAuthIdentifier();
public function getAuthPasswordName();
public function getAuthPassword();
public function getRememberToken();
public function setRememberToken($value);
public function getRememberTokenName();
}
此介面很簡單。getAuthIdentifierName 方法應返回使用者的「主鍵」欄位的名稱,getAuthIdentifier 方法應返回使用者的「主鍵」。使用 MySQL 後端時,這通常是分配給使用者記錄的自動遞增主鍵。getAuthPasswordName 方法應返回使用者的密碼欄位名稱。getAuthPassword 方法應返回使用者的雜湊密碼。
此介面允許認證系統與任何「使用者」類別一起運作,無論您使用什麼 ORM 或儲存抽象層。預設情況下,Laravel 在 app/Models 目錄中包含一個實現此介面的 App\Models\User 類別。
自動密碼重新雜湊
Laravel 的預設密碼雜湊演算法是 bcrypt。可以透過應用程式的 config/hashing.php 配置檔案或 BCRYPT_ROUNDS 環境變數來調整 bcrypt 雜湊的「工作因子」。
通常,隨著 CPU / GPU 處理能力的增加,應隨時間增加 bcrypt 工作因子。如果您增加了應用程式的 bcrypt 工作因子,Laravel 將在使用者透過 Laravel 的入門套件或當您透過 attempt 方法手動認證使用者時優雅且自動地重新雜湊使用者密碼。
通常,自動密碼重新雜湊不應中斷您的應用程式;但是,您可以透過發佈 hashing 配置檔案來禁用此行為:
php artisan config:publish hashing
發佈配置檔案後,您可以將 rehash_on_login 配置值設定為 false:
'rehash_on_login' => false,
事件
Laravel 在認證過程中派發各種事件。您可以為以下任何事件定義監聽器:
| 事件名稱 |
|---|
Illuminate\Auth\Events\Registered |
Illuminate\Auth\Events\Attempting |
Illuminate\Auth\Events\Authenticated |
Illuminate\Auth\Events\Login |
Illuminate\Auth\Events\Failed |
Illuminate\Auth\Events\Validated |
Illuminate\Auth\Events\Verified |
Illuminate\Auth\Events\Logout |
Illuminate\Auth\Events\CurrentDeviceLogout |
Illuminate\Auth\Events\OtherDeviceLogout |
Illuminate\Auth\Events\Lockout |
Illuminate\Auth\Events\PasswordReset |
Illuminate\Auth\Events\PasswordResetLinkSent |