跳到內容

Laravel Sanctum

簡介

Laravel Sanctum 為 SPA(單頁應用程式)、行動應用程式和基於令牌的簡單 API 提供輕量級身份驗證系統。 Sanctum 允許您的應用程式的每個使用者為其帳戶產生多個 API 令牌。 這些令牌可以被授予能力/範圍,以指定允許令牌執行的操作。

運作方式

Laravel Sanctum 的存在是為了解決兩個獨立的問題。 在深入研究該函式庫之前,讓我們先討論一下每個問題。

API 令牌

首先,Sanctum 是一個簡單的套件,您可以使用它向使用者發行 API 令牌,而無需 OAuth 的複雜性。 此功能的靈感來自 GitHub 和其他發行「個人存取令牌」的應用程式。 例如,想像一下您應用程式的「帳戶設定」有一個畫面,使用者可以在其中為其帳戶產生 API 令牌。 您可以使用 Sanctum 來產生和管理這些令牌。 這些令牌通常具有非常長的到期時間(數年),但使用者可以隨時手動撤銷它們。

Laravel Sanctum 通過在單個資料庫表中儲存使用者 API 令牌,並通過 Authorization 標頭驗證傳入的 HTTP 請求來提供此功能,該標頭應包含有效的 API 令牌。

SPA 驗證

其次,Sanctum 的存在是為了提供一種簡單的方式來驗證需要與 Laravel 驅動的 API 通信的單頁應用程式 (SPA)。 這些 SPA 可能與您的 Laravel 應用程式位於同一個儲存庫中,或者可能是完全不同的儲存庫,例如使用 Next.js 或 Nuxt 建立的 SPA。

對於此功能,Sanctum 不使用任何類型的令牌。 相反,Sanctum 使用 Laravel 內建的基於 Cookie 的會話身份驗證服務。 通常,Sanctum 利用 Laravel 的 web 身份驗證守衛來完成此操作。 這提供了 CSRF 保護、會話身份驗證的好處,並防止通過 XSS 洩露身份驗證憑證。

僅當傳入請求源自您自己的 SPA 前端時,Sanctum 才會嘗試使用 Cookie 進行身份驗證。 當 Sanctum 檢查傳入的 HTTP 請求時,它將首先檢查身份驗證 Cookie,如果不存在,Sanctum 將檢查 Authorization 標頭以尋找有效的 API 令牌。

僅將 Sanctum 用於 API 令牌身份驗證或僅用於 SPA 身份驗證是完全可以的。 僅僅因為您使用 Sanctum 並不意味著您必須使用它提供的所有功能。

安裝

您可以通過 install:api Artisan 命令安裝 Laravel Sanctum

1php artisan install:api

接下來,如果您計劃使用 Sanctum 來驗證 SPA,請參閱本文檔的SPA 身份驗證部分。

設定

覆寫預設模型

儘管通常不是必需的,但您可以自由擴展 Sanctum 內部使用的 PersonalAccessToken 模型

1use Laravel\Sanctum\PersonalAccessToken as SanctumPersonalAccessToken;
2 
3class PersonalAccessToken extends SanctumPersonalAccessToken
4{
5 // ...
6}

然後,您可以通過 Sanctum 提供的 usePersonalAccessTokenModel 方法指示 Sanctum 使用您的自訂模型。 通常,您應該在應用程式的 AppServiceProvider 檔案的 boot 方法中呼叫此方法

1use App\Models\Sanctum\PersonalAccessToken;
2use Laravel\Sanctum\Sanctum;
3 
4/**
5 * Bootstrap any application services.
6 */
7public function boot(): void
8{
9 Sanctum::usePersonalAccessTokenModel(PersonalAccessToken::class);
10}

API 令牌驗證

您不應使用 API 令牌來驗證您自己的第一方 SPA。 相反,請使用 Sanctum 的內建 SPA 身份驗證功能

發行 API 令牌

Sanctum 允許您發行 API 令牌/個人存取令牌,這些令牌可用於驗證對您的應用程式的 API 請求。 使用 API 令牌發出請求時,令牌應包含在 Authorization 標頭中作為 Bearer 令牌。

要開始為使用者發行令牌,您的 User 模型應使用 Laravel\Sanctum\HasApiTokens trait

1use Laravel\Sanctum\HasApiTokens;
2 
3class User extends Authenticatable
4{
5 use HasApiTokens, HasFactory, Notifiable;
6}

要發行令牌,您可以使用 createToken 方法。 createToken 方法返回 Laravel\Sanctum\NewAccessToken 實例。 API 令牌在使用 SHA-256 雜湊儲存到資料庫之前進行雜湊處理,但您可以使用 NewAccessToken 實例的 plainTextToken 屬性存取令牌的純文字值。 您應該在建立令牌後立即向使用者顯示此值

1use Illuminate\Http\Request;
2 
3Route::post('/tokens/create', function (Request $request) {
4 $token = $request->user()->createToken($request->token_name);
5 
6 return ['token' => $token->plainTextToken];
7});

您可以使用 HasApiTokens trait 提供的 tokens Eloquent 關聯存取使用者的所有令牌

1foreach ($user->tokens as $token) {
2 // ...
3}

令牌能力

Sanctum 允許您為令牌分配「能力」。 能力的作用與 OAuth 的「範圍」類似。 您可以將字串能力陣列作為第二個參數傳遞給 createToken 方法

1return $user->createToken('token-name', ['server:update'])->plainTextToken;

當處理由 Sanctum 驗證的傳入請求時,您可以使用 tokenCantokenCant 方法確定令牌是否具有給定的能力

1if ($user->tokenCan('server:update')) {
2 // ...
3}
4 
5if ($user->tokenCant('server:update')) {
6 // ...
7}

令牌能力中介層

Sanctum 還包括兩個中介層,可用於驗證傳入的請求是否使用已被授予給定能力的令牌進行身份驗證。 若要開始使用,請在應用程式的 bootstrap/app.php 檔案中定義以下中介層別名

1use Laravel\Sanctum\Http\Middleware\CheckAbilities;
2use Laravel\Sanctum\Http\Middleware\CheckForAnyAbility;
3 
4->withMiddleware(function (Middleware $middleware) {
5 $middleware->alias([
6 'abilities' => CheckAbilities::class,
7 'ability' => CheckForAnyAbility::class,
8 ]);
9})

abilities 中介層可以分配給路由,以驗證傳入請求的令牌是否具有所有列出的能力

1Route::get('/orders', function () {
2 // Token has both "check-status" and "place-orders" abilities...
3})->middleware(['auth:sanctum', 'abilities:check-status,place-orders']);

ability 中介層可以分配給路由,以驗證傳入請求的令牌是否具有至少一個列出的能力

1Route::get('/orders', function () {
2 // Token has the "check-status" or "place-orders" ability...
3})->middleware(['auth:sanctum', 'ability:check-status,place-orders']);

第一方 UI 發起的請求

為了方便起見,如果傳入的經過身份驗證的請求來自您的第一方 SPA 並且您正在使用 Sanctum 的內建 SPA 身份驗證,則 tokenCan 方法始終返回 true

但是,這並不一定意味著您的應用程式必須允許使用者執行該操作。 通常,您的應用程式的授權策略將確定令牌是否已被授予執行能力的權限,並檢查是否應允許使用者實例本身執行該操作。

例如,如果我們想像一個管理伺服器的應用程式,這可能意味著檢查令牌是否被授權更新伺服器並且伺服器屬於使用者

1return $request->user()->id === $server->user_id &&
2 $request->user()->tokenCan('server:update')

起初,允許呼叫 tokenCan 方法並始終為第一方 UI 發起的請求返回 true 可能看起來很奇怪; 但是,能夠始終假設 API 令牌可用並且可以通過 tokenCan 方法進行檢查是很方便的。 通過採用這種方法,您可以始終在應用程式的授權策略中呼叫 tokenCan 方法,而無需擔心請求是從應用程式的 UI 觸發還是由 API 的第三方消費者之一發起的。

保護路由

為了保護路由,以便所有傳入的請求都必須經過身份驗證,您應該將 sanctum 身份驗證守衛附加到 routes/web.phproutes/api.php 路由檔案中的受保護路由。 此守衛將確保傳入的請求被驗證為有狀態的、Cookie 驗證的請求,或者如果請求來自第三方,則包含有效的 API 令牌標頭。

您可能想知道為什麼我們建議您使用 sanctum 守衛驗證應用程式的 routes/web.php 檔案中的路由。 請記住,Sanctum 將首先嘗試使用 Laravel 的典型會話身份驗證 Cookie 來驗證傳入的請求。 如果該 Cookie 不存在,則 Sanctum 將嘗試使用請求的 Authorization 標頭中的令牌驗證請求。 此外,使用 Sanctum 驗證所有請求可確保我們始終可以在目前經過身份驗證的使用者實例上呼叫 tokenCan 方法

1use Illuminate\Http\Request;
2 
3Route::get('/user', function (Request $request) {
4 return $request->user();
5})->middleware('auth:sanctum');

撤銷令牌

您可以通過使用 Laravel\Sanctum\HasApiTokens trait 提供的 tokens 關聯從資料庫中刪除令牌來「撤銷」令牌

1// Revoke all tokens...
2$user->tokens()->delete();
3 
4// Revoke the token that was used to authenticate the current request...
5$request->user()->currentAccessToken()->delete();
6 
7// Revoke a specific token...
8$user->tokens()->where('id', $tokenId)->delete();

令牌到期

預設情況下,Sanctum 令牌永遠不會過期,並且只能通過撤銷令牌來使其失效。 但是,如果您想為應用程式的 API 令牌配置到期時間,您可以通過應用程式的 sanctum 配置檔案中定義的 expiration 配置選項來執行此操作。 此配置選項定義了發行的令牌將被視為過期之前的分鐘數

1'expiration' => 525600,

如果您想獨立指定每個令牌的到期時間,您可以通過將到期時間作為第三個參數提供給 createToken 方法來執行此操作

1return $user->createToken(
2 'token-name', ['*'], now()->addWeek()
3)->plainTextToken;

如果您已為應用程式配置了令牌到期時間,您可能還希望排程任務來修剪應用程式的過期令牌。 值得慶幸的是,Sanctum 包含一個 sanctum:prune-expired Artisan 命令,您可以使用它來完成此操作。 例如,您可以配置一個排程任務,以刪除所有已過期至少 24 小時的過期令牌資料庫記錄

1use Illuminate\Support\Facades\Schedule;
2 
3Schedule::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 標頭以及 RefererOrigin 標頭。

設定

設定您的第一方網域

首先,您應該設定您的 SPA 將從哪些網域發出請求。您可以使用 sanctum 設定檔中的 stateful 設定選項來設定這些網域。此設定決定哪些網域在向您的 API 發出請求時,將使用 Laravel Session Cookie 維持「具狀態」驗證。

如果您透過包含端口的 URL (127.0.0.1:8000) 存取您的應用程式,您應確保在網域中包含端口號碼。

Sanctum 中介層

接下來,您應該指示 Laravel,來自您 SPA 的傳入請求可以使用 Laravel 的 Session Cookie 進行驗證,同時仍然允許來自第三方或行動應用程式的請求使用 API 令牌進行驗證。這可以透過在您應用程式的 bootstrap/app.php 檔案中調用 statefulApi 中介層方法輕鬆完成

1->withMiddleware(function (Middleware $middleware) {
2 $middleware->statefulApi();
3})

CORS 與 Cookie

如果您在從在不同子網域上執行的 SPA 驗證您的應用程式時遇到問題,則可能是您錯誤設定了 CORS (跨來源資源共享) 或 Session Cookie 設定。

config/cors.php 設定檔預設未發布。如果您需要自訂 Laravel 的 CORS 選項,您應該使用 config:publish Artisan 命令發布完整的 cors 設定檔

1php artisan config:publish cors

接下來,您應該確保您應用程式的 CORS 設定正在返回 Access-Control-Allow-Credentials 標頭,其值為 True。這可以透過將您應用程式 config/cors.php 設定檔中的 supports_credentials 選項設定為 true 來完成。

此外,您應該在您應用程式的全局 axios 實例上啟用 withCredentialswithXSRFToken 選項。通常,這應該在您的 resources/js/bootstrap.js 檔案中執行。如果您未使用 Axios 從您的前端發出 HTTP 請求,則應在您自己的 HTTP 客戶端上執行等效的設定

1axios.defaults.withCredentials = true;
2axios.defaults.withXSRFToken = true;

最後,您應確保您應用程式的 Session Cookie 網域設定支援您根網域的任何子網域。您可以透過在您應用程式的 config/session.php 設定檔中,在網域前加上前導 . 來完成此操作

1'domain' => '.domain.com',

驗證

CSRF 保護

為了驗證您的 SPA,您的 SPA 的「登入」頁面應首先向 /sanctum/csrf-cookie 端點發出請求,以初始化應用程式的 CSRF 保護

1axios.get('/sanctum/csrf-cookie').then(response => {
2 // Login...
3});

在此請求期間,Laravel 將設定一個包含當前 CSRF 令牌的 XSRF-TOKEN Cookie。然後應對此令牌進行 URL 解碼,並在後續請求中的 X-XSRF-TOKEN 標頭中傳遞,某些 HTTP 客戶端程式庫(如 Axios 和 Angular HttpClient)會自動為您執行此操作。如果您的 JavaScript HTTP 程式庫未為您設定該值,您將需要手動設定 X-XSRF-TOKEN 標頭,以匹配此路由設定的 XSRF-TOKEN Cookie 的 URL 解碼值。

登入

一旦 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 令牌標頭

1use Illuminate\Http\Request;
2 
3Route::get('/user', function (Request $request) {
4 return $request->user();
5})->middleware('auth:sanctum');

授權私有廣播頻道

如果您的 SPA 需要使用 私有 / 存在廣播頻道 進行驗證,您應從您應用程式 bootstrap/app.php 檔案中包含的 withRouting 方法中移除 channels 條目。相反,您應調用 withBroadcasting 方法,以便您可以為您應用程式的廣播路由指定正確的中介層

1return Application::configure(basePath: dirname(__DIR__))
2 ->withRouting(
3 web: __DIR__.'/../routes/web.php',
4 // ...
5 )
6 ->withBroadcasting(
7 __DIR__.'/../routes/channels.php',
8 ['prefix' => 'api', 'middleware' => ['api', 'auth:sanctum']],
9 )

接下來,為了使 Pusher 的授權請求成功,您需要在初始化 Laravel Echo 時提供自訂的 Pusher authorizer。這允許您的應用程式設定 Pusher 以使用 針對跨網域請求正確設定的 axios 實例

1window.Echo = new Echo({
2 broadcaster: "pusher",
3 cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER,
4 encrypted: true,
5 key: import.meta.env.VITE_PUSHER_APP_KEY,
6 authorizer: (channel, options) => {
7 return {
8 authorize: (socketId, callback) => {
9 axios.post('/api/broadcasting/auth', {
10 socket_id: socketId,
11 channel_name: channel.name
12 })
13 .then(response => {
14 callback(false, response.data);
15 })
16 .catch(error => {
17 callback(true, error);
18 });
19 }
20 };
21 },
22})

行動應用程式驗證

您也可以使用 Sanctum 令牌來驗證您的行動應用程式對您的 API 的請求。驗證行動應用程式請求的過程與驗證第三方 API 請求類似;但是,在發行 API 令牌的方式上存在細微差異。

發行 API 令牌

首先,建立一個路由,該路由接受使用者的電子郵件 / 使用者名稱、密碼和裝置名稱,然後將這些憑證交換為新的 Sanctum 令牌。給定此端點的「裝置名稱」僅供參考,可以是您希望的任何值。一般來說,裝置名稱值應為使用者可以識別的名稱,例如「Nuno 的 iPhone 12」。

通常,您將從您的行動應用程式的「登入」畫面發出對令牌端點的請求。該端點將返回純文字 API 令牌,然後可以將其儲存在行動裝置上,並用於發出其他 API 請求

1use App\Models\User;
2use Illuminate\Http\Request;
3use Illuminate\Support\Facades\Hash;
4use Illuminate\Validation\ValidationException;
5 
6Route::post('/sanctum/token', function (Request $request) {
7 $request->validate([
8 'email' => 'required|email',
9 'password' => 'required',
10 'device_name' => 'required',
11 ]);
12 
13 $user = User::where('email', $request->email)->first();
14 
15 if (! $user || ! Hash::check($request->password, $user->password)) {
16 throw ValidationException::withMessages([
17 'email' => ['The provided credentials are incorrect.'],
18 ]);
19 }
20 
21 return $user->createToken($request->device_name)->plainTextToken;
22});

當行動應用程式使用令牌向您的應用程式發出 API 請求時,它應在 Authorization 標頭中以 Bearer 令牌的形式傳遞令牌。

在為行動應用程式發行令牌時,您也可以自由指定 令牌能力

保護路由

如先前文檔所述,您可以保護路由,以便所有傳入請求都必須經過驗證,方法是將 sanctum 驗證守衛附加到路由

1Route::get('/user', function (Request $request) {
2 return $request->user();
3})->middleware('auth:sanctum');

撤銷令牌

為了允許使用者撤銷發行給行動裝置的 API 令牌,您可以在您 Web 應用程式 UI 的「帳戶設定」部分中,按名稱列出它們以及「撤銷」按鈕。當使用者點擊「撤銷」按鈕時,您可以從資料庫中刪除令牌。請記住,您可以透過 Laravel\Sanctum\HasApiTokens 特徵提供的 tokens 關係存取使用者的 API 令牌

1// Revoke all tokens...
2$user->tokens()->delete();
3 
4// Revoke a specific token...
5$user->tokens()->where('id', $tokenId)->delete();

測試

在測試時,可以使用 Sanctum::actingAs 方法來驗證使用者並指定應授予其令牌哪些能力

1use App\Models\User;
2use Laravel\Sanctum\Sanctum;
3 
4test('task list can be retrieved', function () {
5 Sanctum::actingAs(
6 User::factory()->create(),
7 ['view-tasks']
8 );
9 
10 $response = $this->get('/api/task');
11 
12 $response->assertOk();
13});
1use App\Models\User;
2use Laravel\Sanctum\Sanctum;
3 
4public function test_task_list_can_be_retrieved(): void
5{
6 Sanctum::actingAs(
7 User::factory()->create(),
8 ['view-tasks']
9 );
10 
11 $response = $this->get('/api/task');
12 
13 $response->assertOk();
14}

如果您想授予令牌所有能力,您應在提供給 actingAs 方法的能力列表中包含 *

1Sanctum::actingAs(
2 User::factory()->create(),
3 ['*']
4);

Laravel 是最有效率的方式來
建構、部署和監控軟體。