Laravel Sanctum
簡介
Laravel Sanctum 為 SPA(單頁應用程式)、行動應用程式和簡單的基於 Token 的 API 提供了一個輕量級的身份驗證系統。Sanctum 允許您的應用程式的每個使用者為他們的帳戶產生多個 API Token。這些 Token 可以被授予權限 / 範圍,指定這些 Token 可以執行哪些操作。
運作方式
Laravel Sanctum 旨在解決兩個獨立的問題。在深入探討這個函式庫之前,我們先來討論一下每個問題。
API Token
首先,Sanctum 是一個簡單的套件,您可以使用它向使用者發行 API Token,而無需 OAuth 的複雜性。此功能的靈感來自 GitHub 和其他發行「個人存取 Token」的應用程式。例如,想像一下您應用程式的「帳戶設定」中,有一個畫面允許使用者為其帳戶產生 API Token。您可以使用 Sanctum 來產生和管理這些 Token。這些 Token 通常具有非常長的到期時間(數年),但使用者可以隨時手動撤銷它們。
Laravel Sanctum 透過在單一資料庫表格中儲存使用者 API Token,並透過 Authorization
標頭(應包含有效的 API Token)驗證傳入的 HTTP 請求,來提供此功能。
SPA 驗證
其次,Sanctum 旨在提供一種簡單的方式來驗證需要與 Laravel 支援的 API 通訊的單頁應用程式(SPA)。這些 SPA 可能與您的 Laravel 應用程式存在於同一個儲存庫中,也可能是一個完全獨立的儲存庫,例如使用 Next.js 或 Nuxt 建立的 SPA。
對於此功能,Sanctum 不使用任何類型的 Token。相反,Sanctum 使用 Laravel 內建的基於 Cookie 的 Session 驗證服務。通常,Sanctum 利用 Laravel 的 web
驗證守衛來實現此目的。這提供了 CSRF 保護、Session 驗證的好處,並防止通過 XSS 洩漏驗證憑證。
當傳入的請求來自您自己的 SPA 前端時,Sanctum 才會嘗試使用 Cookie 進行驗證。當 Sanctum 檢查傳入的 HTTP 請求時,它會先檢查是否存在驗證 Cookie,如果不存在,Sanctum 會接著檢查 Authorization
標頭是否存在有效的 API Token。
僅將 Sanctum 用於 API Token 驗證或僅用於 SPA 驗證是完全沒有問題的。僅僅因為您使用 Sanctum,並不表示您必須使用它提供的兩個功能。
安裝
您可以透過 install:api
Artisan 指令安裝 Laravel Sanctum
php artisan install:api
接下來,如果您計劃使用 Sanctum 來驗證 SPA,請參考本文件的 SPA 驗證部分。
設定
覆寫預設模型
雖然通常不是必需的,但您可以自由擴展 Sanctum 內部使用的 PersonalAccessToken
模型
use Laravel\Sanctum\PersonalAccessToken as SanctumPersonalAccessToken; class PersonalAccessToken extends SanctumPersonalAccessToken{ // ...}
然後,您可以透過 Sanctum 提供的 usePersonalAccessTokenModel
方法,指示 Sanctum 使用您的自訂模型。通常,您應該在應用程式 AppServiceProvider
檔案的 boot
方法中呼叫此方法
use App\Models\Sanctum\PersonalAccessToken;use Laravel\Sanctum\Sanctum; /** * Bootstrap any application services. */public function boot(): void{ Sanctum::usePersonalAccessTokenModel(PersonalAccessToken::class);}
API Token 驗證
您不應該使用 API Token 來驗證您自己的第一方 SPA。相反,請使用 Sanctum 的內建 SPA 驗證功能。
發行 API Token
Sanctum 允許您發行 API Token / 個人存取 Token,這些 Token 可用於驗證對您應用程式的 API 請求。在使用 API Token 發出請求時,Token 應作為 Bearer
Token 包含在 Authorization
標頭中。
若要開始為使用者發行 Token,您的使用者模型應使用 Laravel\Sanctum\HasApiTokens
trait
use Laravel\Sanctum\HasApiTokens; class User extends Authenticatable{ use HasApiTokens, HasFactory, Notifiable;}
若要發行 Token,您可以使用 createToken
方法。createToken
方法會傳回 Laravel\Sanctum\NewAccessToken
實例。API Token 會在使用 SHA-256 雜湊演算法雜湊後儲存在您的資料庫中,但您可以使用 NewAccessToken
實例的 plainTextToken
屬性存取 Token 的純文字值。您應該在 Token 建立後立即向使用者顯示此值
use Illuminate\Http\Request; Route::post('/tokens/create', function (Request $request) { $token = $request->user()->createToken($request->token_name); return ['token' => $token->plainTextToken];});
您可以使用 HasApiTokens
trait 提供的 tokens
Eloquent 關係存取所有使用者的 Token
foreach ($user->tokens as $token) { // ...}
Token 權限
Sanctum 允許您為 Token 指派「權限」。權限的作用與 OAuth 的「範圍」類似。您可以將字串權限陣列作為第二個引數傳遞給 createToken
方法
return $user->createToken('token-name', ['server:update'])->plainTextToken;
在處理使用 Sanctum 驗證的傳入請求時,您可以使用 tokenCan
方法判斷 Token 是否具有給定的權限
if ($user->tokenCan('server:update')) { // ...}
Token 權限中介層
Sanctum 還包括兩個中介層,可用於驗證傳入的請求是否已使用已授予給定權限的 Token 進行驗證。若要開始,請在應用程式的 bootstrap/app.php
檔案中定義下列中介層別名
use Laravel\Sanctum\Http\Middleware\CheckAbilities;use Laravel\Sanctum\Http\Middleware\CheckForAnyAbility; ->withMiddleware(function (Middleware $middleware) { $middleware->alias([ 'abilities' => CheckAbilities::class, 'ability' => CheckForAnyAbility::class, ]);})
可以將 abilities
中介層指派給路由,以驗證傳入請求的 Token 是否具有所有列出的權限
Route::get('/orders', function () { // Token has both "check-status" and "place-orders" abilities...})->middleware(['auth:sanctum', 'abilities:check-status,place-orders']);
可以將 ability
中介層指派給路由,以驗證傳入請求的 Token 是否具有至少一個列出的權限
Route::get('/orders', function () { // Token has the "check-status" or "place-orders" ability...})->middleware(['auth:sanctum', 'ability:check-status,place-orders']);
第一方 UI 發起的請求
為了方便起見,如果傳入的已驗證請求來自您的第一方 SPA,並且您正在使用 Sanctum 的內建 SPA 驗證,tokenCan
方法將始終傳回 true
。
然而,這並不一定表示您的應用程式必須允許使用者執行該動作。通常,您應用程式的授權策略會決定權杖是否被授予執行這些能力的權限,同時也會檢查使用者實例本身是否應該被允許執行該動作。
舉例來說,如果我們想像一個管理伺服器的應用程式,這可能表示要檢查權杖是否有權限更新伺服器並且該伺服器是否屬於該使用者。
return $request->user()->id === $server->user_id && $request->user()->tokenCan('server:update')
起初,允許呼叫 tokenCan
方法並針對第一方 UI 發起的請求總是返回 true
可能看起來很奇怪;然而,能夠總是假設 API 權杖可用,並可透過 tokenCan
方法檢查,是很方便的。透過這種方式,您可以在應用程式的授權策略中總是呼叫 tokenCan
方法,而無需擔心請求是從應用程式的 UI 觸發,還是由 API 的第三方消費者發起的。
保護路由
為了保護路由,使所有傳入的請求都必須經過驗證,您應該將 sanctum
驗證守衛附加到 routes/web.php
和 routes/api.php
路由檔案中的受保護路由。此守衛將確保傳入的請求通過狀態式(有狀態的)、Cookie 驗證的請求驗證,或者如果請求來自第三方,則包含有效的 API 權杖標頭。
您可能想知道為什麼我們建議您使用 sanctum
守衛來驗證應用程式的 routes/web.php
檔案中的路由。請記住,Sanctum 將首先嘗試使用 Laravel 的典型 Session 驗證 Cookie 來驗證傳入的請求。如果不存在該 Cookie,則 Sanctum 將嘗試使用請求的 Authorization
標頭中的權杖來驗證請求。此外,使用 Sanctum 驗證所有請求可確保我們始終可以在當前已驗證的使用者實例上呼叫 tokenCan
方法。
use Illuminate\Http\Request; Route::get('/user', function (Request $request) { return $request->user();})->middleware('auth:sanctum');
撤銷 Token
您可以使用 Laravel\Sanctum\HasApiTokens
trait 提供的 tokens
關係,從資料庫中刪除權杖來「撤銷」權杖。
// Revoke all tokens...$user->tokens()->delete(); // Revoke the token that was used to authenticate the current request...$request->user()->currentAccessToken()->delete(); // Revoke a specific token...$user->tokens()->where('id', $tokenId)->delete();
Token 過期
預設情況下,Sanctum 權杖永遠不會過期,只能透過撤銷權杖來使其失效。但是,如果您想為應用程式的 API 權杖設定過期時間,您可以透過應用程式 sanctum
設定檔案中定義的 expiration
設定選項來進行設定。此設定選項定義了發出的權杖被視為過期之前的分鐘數。
'expiration' => 525600,
如果您想個別指定每個權杖的過期時間,您可以將過期時間作為第三個參數提供給 createToken
方法。
return $user->createToken( 'token-name', ['*'], now()->addWeek())->plainTextToken;
如果您已為應用程式設定權杖過期時間,您可能還希望排程任務來修剪應用程式的過期權杖。幸運的是,Sanctum 包含一個 sanctum:prune-expired
Artisan 命令,您可以使用它來完成此操作。例如,您可以設定一個排程任務,以刪除所有已過期至少 24 小時的過期權杖資料庫記錄。
use Illuminate\Support\Facades\Schedule; Schedule::command('sanctum:prune-expired --hours=24')->daily();
SPA 驗證
Sanctum 的存在也是為了提供一種簡單的方法來驗證需要與 Laravel 驅動的 API 通訊的單頁應用程式 (SPA)。這些 SPA 可能與 Laravel 應用程式存在於同一個儲存庫中,或者可能是一個完全獨立的儲存庫。
對於此功能,Sanctum 不使用任何類型的權杖。相反地,Sanctum 使用 Laravel 內建的基於 Cookie 的 Session 驗證服務。這種驗證方法提供了 CSRF 保護、Session 驗證的好處,並防止透過 XSS 洩漏驗證憑證。
為了進行驗證,您的 SPA 和 API 必須共享相同的高層網域。但是,它們可以放置在不同的子網域上。此外,您應該確保您在請求中發送 Accept: application/json
標頭以及 Referer
或 Origin
標頭。
設定
設定您的第一方網域
首先,您應該設定您的 SPA 將從哪些網域發出請求。您可以使用 sanctum
設定檔案中的 stateful
設定選項來設定這些網域。此設定決定了在向 API 發出請求時,哪些網域將使用 Laravel Session Cookie 維護「狀態式」驗證。
如果您是通過包含連接埠的 URL (127.0.0.1:8000
) 存取您的應用程式,則應確保在網域中包含連接埠號碼。
Sanctum 中介層
接下來,您應該指示 Laravel,來自您的 SPA 的傳入請求可以使用 Laravel 的 Session Cookie 進行驗證,同時仍允許來自第三方或行動應用程式的請求使用 API 權杖進行驗證。這可以透過在應用程式的 bootstrap/app.php
檔案中調用 statefulApi
中介層方法來輕鬆實現。
->withMiddleware(function (Middleware $middleware) { $middleware->statefulApi();})
CORS 和 Cookie
如果您在從單獨子網域上執行的 SPA 驗證應用程式時遇到問題,則可能是您錯誤地設定了 CORS(跨來源資源共享)或 Session Cookie 設定。
預設情況下,不會發佈 config/cors.php
設定檔案。如果您需要自訂 Laravel 的 CORS 選項,則應使用 config:publish
Artisan 命令發佈完整的 cors
設定檔案。
php artisan config:publish cors
接下來,您應該確保應用程式的 CORS 設定返回 Access-Control-Allow-Credentials
標頭,其值為 True
。這可以透過將應用程式 config/cors.php
設定檔案中的 supports_credentials
選項設定為 true
來完成。
此外,您應該在應用程式的全域 axios
實例上啟用 withCredentials
和 withXSRFToken
選項。通常,這應在您的 resources/js/bootstrap.js
檔案中執行。如果您沒有使用 Axios 從前端發出 HTTP 請求,則應在您自己的 HTTP 客戶端上執行等效的設定。
axios.defaults.withCredentials = true;axios.defaults.withXSRFToken = true;
最後,您應該確保應用程式的 Session Cookie 網域設定支援根網域的任何子網域。您可以在應用程式的 config/session.php
設定檔案中,在網域前面加上前導 .
來完成此操作。
'domain' => '.domain.com',
驗證
CSRF 保護
要驗證您的 SPA,您的 SPA 的「登入」頁面應首先向 /sanctum/csrf-cookie
端點發出請求,以初始化應用程式的 CSRF 保護。
axios.get('/sanctum/csrf-cookie').then(response => { // Login...});
在此請求期間,Laravel 將設定一個包含目前 CSRF 權杖的 XSRF-TOKEN
Cookie。然後,此權杖應在後續請求中以 X-XSRF-TOKEN
標頭傳遞,某些 HTTP 客戶端程式庫(例如 Axios 和 Angular HttpClient)會自動為您執行此操作。如果您的 JavaScript HTTP 程式庫未設定該值,則您需要手動將 X-XSRF-TOKEN
標頭設定為與此路由設定的 XSRF-TOKEN
Cookie 的值相符。
登入
初始化 CSRF 保護後,您應該向 Laravel 應用程式的 /login
路由發出 POST
請求。此 /login
路由可以手動實作,或者使用類似 Laravel Fortify 的無頭驗證套件實作。
如果登入請求成功,您將會通過驗證,並且後續對應用程式路由的請求將會通過 Laravel 應用程式發送給您的客戶端的 Session Cookie 自動驗證。此外,由於您的應用程式已向 /sanctum/csrf-cookie
路由發出請求,因此只要您的 JavaScript HTTP 客戶端在 X-XSRF-TOKEN
標頭中傳送 XSRF-TOKEN
Cookie 的值,後續請求應自動收到 CSRF 保護。
當然,如果您的使用者的 Session 因缺乏活動而過期,則後續對 Laravel 應用程式的請求可能會收到 401 或 419 HTTP 錯誤回應。在這種情況下,您應該將使用者重新導向到 SPA 的登入頁面。
您可以自由編寫自己的 /login
端點;但是,您應該確保它使用標準的Laravel 提供的基於 Session 的驗證服務來驗證使用者。通常,這表示使用 web
驗證守衛。
保護路由
為了保護路由,使所有傳入的請求都必須經過驗證,您應該將 sanctum
驗證守衛附加到 routes/api.php
檔案中的 API 路由。此守衛將確保傳入的請求通過來自您的 SPA 的狀態式驗證請求進行驗證,或者如果請求來自第三方,則包含有效的 API 權杖標頭。
use Illuminate\Http\Request; Route::get('/user', function (Request $request) { return $request->user();})->middleware('auth:sanctum');
授權私人廣播頻道
如果您的 SPA 需要通過私有/存在廣播頻道進行驗證,則應從應用程式 bootstrap/app.php
檔案中包含的 withRouting
方法中移除 channels
項目。相反地,您應該調用 withBroadcasting
方法,以便您可以為應用程式的廣播路由指定正確的中介層。
return Application::configure(basePath: dirname(__DIR__)) ->withRouting( web: __DIR__.'/../routes/web.php', // ... ) ->withBroadcasting( __DIR__.'/../routes/channels.php', ['prefix' => 'api', 'middleware' => ['api', 'auth:sanctum']], )
接下來,為了使 Pusher 的授權請求成功,您需要在初始化 Laravel Echo 時提供自訂的 Pusher authorizer
。這允許您的應用程式將 Pusher 設定為使用針對跨網域請求正確設定的 axios
實例。
window.Echo = new Echo({ broadcaster: "pusher", cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER, encrypted: true, key: import.meta.env.VITE_PUSHER_APP_KEY, authorizer: (channel, options) => { return { authorize: (socketId, callback) => { axios.post('/api/broadcasting/auth', { socket_id: socketId, channel_name: channel.name }) .then(response => { callback(false, response.data); }) .catch(error => { callback(true, error); }); } }; },})
行動應用程式驗證
您也可以使用 Sanctum 權杖來驗證您的行動應用程式對 API 的請求。驗證行動應用程式請求的過程與驗證第三方 API 請求類似;但是,在發放 API 權杖的方式上存在細微差異。
發行 API Token
首先,建立一個接受使用者的電子郵件/使用者名稱、密碼和裝置名稱的路由,然後將這些憑證交換為新的 Sanctum 權杖。給予此端點的「裝置名稱」僅供參考,可以是您想要的任何值。一般而言,裝置名稱值應為使用者可以辨識的名稱,例如「Nuno 的 iPhone 12」。
通常,您將從行動應用程式的「登入」畫面發出對權杖端點的請求。端點將返回純文字 API 權杖,然後可以將其儲存在行動裝置上,並用於發出其他 API 請求。
use App\Models\User;use Illuminate\Http\Request;use Illuminate\Support\Facades\Hash;use Illuminate\Validation\ValidationException; Route::post('/sanctum/token', function (Request $request) { $request->validate([ 'email' => 'required|email', 'password' => 'required', 'device_name' => 'required', ]); $user = User::where('email', $request->email)->first(); if (! $user || ! Hash::check($request->password, $user->password)) { throw ValidationException::withMessages([ 'email' => ['The provided credentials are incorrect.'], ]); } return $user->createToken($request->device_name)->plainTextToken;});
當行動應用程式使用權杖向您的應用程式發出 API 請求時,它應將權杖以 Bearer
權杖的形式傳遞在 Authorization
標頭中。
當為行動應用程式發行令牌時,您也可以自由指定令牌權限。
保護路由
如先前文件所述,您可以保護路由,使其所有傳入請求都必須通過將 sanctum
驗證守衛附加到路由來進行驗證。
Route::get('/user', function (Request $request) { return $request->user();})->middleware('auth:sanctum');
撤銷 Token
為了讓使用者能夠撤銷發給行動裝置的 API 令牌,您可以在網頁應用程式 UI 的「帳號設定」部分中列出它們的名稱以及「撤銷」按鈕。當使用者點擊「撤銷」按鈕時,您可以從資料庫中刪除該令牌。請記住,您可以透過 Laravel\Sanctum\HasApiTokens
trait 提供的 tokens
關係來存取使用者的 API 令牌。
// Revoke all tokens...$user->tokens()->delete(); // Revoke a specific token...$user->tokens()->where('id', $tokenId)->delete();
測試
在測試時,可以使用 Sanctum::actingAs
方法來驗證使用者,並指定應該授予其令牌的權限。
use App\Models\User;use Laravel\Sanctum\Sanctum; test('task list can be retrieved', function () { Sanctum::actingAs( User::factory()->create(), ['view-tasks'] ); $response = $this->get('/api/task'); $response->assertOk();});
use App\Models\User;use Laravel\Sanctum\Sanctum; public function test_task_list_can_be_retrieved(): void{ Sanctum::actingAs( User::factory()->create(), ['view-tasks'] ); $response = $this->get('/api/task'); $response->assertOk();}
如果您想將所有權限都授予令牌,您應該在提供給 actingAs
方法的權限列表中包含 *
。
Sanctum::actingAs( User::factory()->create(), ['*']);