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 權杖。
[!NOTE]
只使用 Sanctum 進行 API 權杖身份驗證或僅用於 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
方法來指定您的自定義模型。通常,您應該在應用程式的 AppServiceProvider
檔案的 boot
方法中呼叫此方法:
use App\Models\Sanctum\PersonalAccessToken;
use Laravel\Sanctum\Sanctum;
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Sanctum::usePersonalAccessTokenModel(PersonalAccessToken::class);
}
API 權杖驗證
[!NOTE]
您不應該使用 API 權杖來驗證您自己的第一方單頁應用程式。請改用 Sanctum 內建的 SPA 驗證功能。
發行 API 權杖
Sanctum 允許您發行 API 權杖 / 個人存取權杖,用於驗證對您應用程式的 API 請求。在使用 API 權杖進行請求時,應將該權杖包含在 Authorization
標頭中,作為 Bearer
權杖。
要開始為使用者發行權杖,您的 User 模型應該使用 Laravel\Sanctum\HasApiTokens
特性:
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable
{
use HasApiTokens, HasFactory, Notifiable;
}
要發行權杖,您可以使用 createToken
方法。createToken
方法會返回一個 Laravel\Sanctum\NewAccessToken
實例。API 權杖在存儲到您的資料庫之前會使用 SHA-256 雜湊進行雜湊,但您可以通過 NewAccessToken
實例的 plainTextToken
屬性來訪問權杖的明文值。您應該在權杖創建後立即向使用者顯示此值:
use Illuminate\Http\Request;
Route::post('/tokens/create', function (Request $request) {
$token = $request->user()->createToken($request->token_name);
return ['token' => $token->plainTextToken];
});
您可以使用 HasApiTokens
特性提供的 tokens
Eloquent 關聯來訪問使用者的所有權杖:
foreach ($user->tokens as $token) {
// ...
}
權杖權限
Sanctum 允許您向權杖分配 "權限"。權限的作用類似於 OAuth 的 "範圍"。您可以將一個字串權限陣列作為 createToken
方法的第二個引數傳遞:
return $user->createToken('token-name', ['server:update'])->plainTextToken;
在處理由 Sanctum 驗證的傳入請求時,您可以使用 tokenCan
或 tokenCant
方法來確定權杖是否具有特定權限:
if ($user->tokenCan('server:update')) {
// ...
}
if ($user->tokenCant('server:update')) {
// ...
}
標記權限中介層
Sanctum 還包括兩個中介層,可用於驗證傳入請求是否使用已被授予特定權限的標記進行身份驗證。要開始,請在應用程式的 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
中介層可分配給路由,以驗證傳入請求的標記是否具有所有列出的權限:
Route::get('/orders', function () {
// 標記同時具有 "check-status" 和 "place-orders" 權限...
})->middleware(['auth:sanctum', 'abilities:check-status,place-orders']);
ability
中介層可分配給路由,以驗證傳入請求的標記是否至少具有列出的一個權限:
Route::get('/orders', function () {
// 標記具有 "check-status" 或 "place-orders" 權限...
})->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 標頭。
也許您會想知道為什麼我們建議您在應用程式的 routes/web.php
文件中使用 sanctum
守衛對路由進行身份驗證。請記住,Sanctum 將首先嘗試使用 Laravel 的典型會話身份驗證 Cookie 來驗證傳入的請求。如果該 Cookie 不存在,那麼 Sanctum 將嘗試使用請求的 Authorization
標頭中的令牌來驗證請求。此外,使用 Sanctum 對所有請求進行身份驗證確保我們始終可以在當前已驗證的使用者實例上調用 tokenCan
方法:
use Illuminate\Http\Request;
Route::get('/user', function (Request $request) {
return $request->user();
})->middleware('auth:sanctum');
撤銷令牌
您可以通過刪除由 Laravel\Sanctum\HasApiTokens
特性提供的 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();
令牌過期
預設情況下,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();
單頁應用程式認證
Sanctum 也存在為提供一個簡單的方法來對需要與 Laravel 驅動的 API 通信的單頁應用程式(SPA)進行身份驗證。這些 SPA 可能存在於與您的 Laravel 應用程式相同的存儲庫中,也可能是一個完全獨立的存儲庫。
對於這個功能,Sanctum 不使用任何類型的標記。相反,Sanctum 使用 Laravel 內建的基於 cookie 的會話身份驗證服務。這種身份驗證方法提供了 CSRF 保護、會話身份驗證的好處,以及防止通過 XSS 洩漏身份驗證憑證的保護。
[!WARNING]
為了進行身份驗證,您的 SPA 和 API 必須共享相同的頂級域。但是,它們可以放置在不同的子域上。此外,您應確保您發送Accept: application/json
標頭以及您的請求中的Referer
或Origin
標頭。
配置
配置您的第一方域
首先,您應該配置您的 SPA 將從哪些域進行請求。您可以使用您的 sanctum
配置檔案中的 stateful
配置選項來配置這些域。此配置設置確定哪些域將在向您的 API 發送請求時使用 Laravel 會話 cookie 進行“有狀態”的身份驗證。
[!WARNING]
如果您通過包含端口號的 URL(127.0.0.1:8000
)訪問應用程式,您應確保將端口號與域名一起包含。
Sanctum Middleware
接下來,您應該指示 Laravel,您的 SPA 發出的請求可以使用 Laravel 的會話 Cookie 進行身份驗證,同時仍允許第三方或移動應用程式使用 API 令牌進行身份驗證。這可以通過在應用程式的 bootstrap/app.php
檔案中調用 statefulApi
中介層方法輕鬆完成:
->withMiddleware(function (Middleware $middleware) {
$middleware->statefulApi();
})
CORS 和 Cookies
如果您在從在不同子域上執行的 SPA 對應用程式進行身份驗證時遇到問題,您可能已錯誤配置了 CORS(跨來源資源共享)或會話 Cookie 設定。
config/cors.php
組態檔並未預設發佈。如果您需要自訂 Laravel 的 CORS 選項,您應該使用 config:publish
Artisan 指令發佈完整的 cors
組態檔:
php artisan config:publish cors
接下來,您應該確保您的應用程式的 CORS 組態返回具有值 True
的 Access-Control-Allow-Credentials
標頭。這可以通過在您的應用程式的 config/cors.php
組態檔中將 supports_credentials
選項設置為 true
來完成。
此外,您應該在您的應用程式的全域 axios
實例上啟用 withCredentials
和 withXSRFToken
選項。通常,這應該在您的 resources/js/bootstrap.js
檔案中執行。如果您未使用 Axios 從前端進行 HTTP 請求,則應在您自己的 HTTP 客戶端上執行等效的組態:
axios.defaults.withCredentials = true;
axios.defaults.withXSRFToken = true;
最後,您應該確保您的應用程式的會話 Cookie 域組態支持您的根域的任何子域。您可以通過在您的應用程式的 config/session.php
組態檔中使用前置 .
來前綴域來完成這一點:
'domain' => '.domain.com',
認證
CSRF 保護
為了認證您的 SPA,您的 SPA 的「登入」頁面應該首先向 /sanctum/csrf-cookie
端點發出請求,以初始化應用程式的 CSRF 保護:
axios.get('/sanctum/csrf-cookie').then(response => {
// 登入...
});
在此請求期間,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 應用程式發送給客戶端的會話 Cookie 進行驗證。此外,由於您的應用程式已經向 /sanctum/csrf-cookie
路由發出請求,後續請求應該自動接收 CSRF 保護,只要您的 JavaScript HTTP 客戶端在 X-XSRF-TOKEN
標頭中發送 XSRF-TOKEN
Cookie 的值。
當然,如果由於活動不足而導致用戶會話過期,對 Laravel 應用程式的後續請求可能會收到 401 或 419 HTTP 錯誤響應。在這種情況下,您應該將用戶重定向到您的 SPA 的登入頁面。
[!WARNING]
您可以自由編寫自己的/login
端點;但是,您應該確保它使用 Laravel 提供的標準基於會話的身份驗證服務對用戶進行身份驗證。通常,這意味著使用web
身份驗證護衛。
保護路由
要保護路由,使所有傳入的請求都必須進行身分驗證,您應該在您的 routes/api.php
檔案中將 sanctum
身分驗證守衛附加到您的 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 令牌
要開始,建立一個路由,接受使用者的電子郵件 / 使用者名稱、密碼和設備名稱,然後將這些憑證交換為新的 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
標頭傳遞。
[!NOTE]
當為行動應用程式發出令牌時,您也可以自由指定 令牌權限。
保護路由
如先前所述,您可以保護路由,以便所有傳入請求必須通過將 sanctum
認證守衛附加到路由來進行身份驗證:
Route::get('/user', function (Request $request) {
return $request->user();
})->middleware('auth:sanctum');
撤銷令牌
為了讓用戶撤銷發放給行動設備的 API 令牌,您可以按名稱列出它們,並在網頁應用程式的 UI 的「帳戶設定」部分中提供一個「撤銷」按鈕。當用戶點擊「撤銷」按鈕時,您可以從資料庫中刪除該令牌。請記住,您可以通過 Laravel\Sanctum\HasApiTokens
特性提供的 tokens
關聯來訪問用戶的 API 令牌:
// Revoke all tokens...
$user->tokens()->delete();
// Revoke a specific token...
$user->tokens()->where('id', $tokenId)->delete();
測試
在測試期間,可以使用 Sanctum::actingAs
方法來驗證用戶並指定其令牌應授予哪些權限:
```php tab=Pest 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();
});
```php tab=PHPUnit
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(),
['*']
);