之前論壇中有人提問過 問答:Sanctum 沒辦法手動設定過期時間吧?
如果要定義 Sanctum token 的過期時間,可以在 config/sanctum.php
中來統一定義。
'expiration' => env("SANCTUM_TTL", 10080),
'refresh_expiration' => env("SANCTUM_REFRESH_TTL", 43200),
這樣的配置 token 的有效期是全域性生效的,例如:
token1 | token2 | token3 |
---|---|---|
120m | 120m | 120m |
但如果我們想要以下的方式呢?
token1 | token2 | token3 |
---|---|---|
10m | 60m | 70m |
要這麼做也很簡單,思路如下:
- 在 Sanctum 遷移檔案中新增
expired_at
欄位。 - 覆寫
HasApiToken
中的createToken
方法。 - 為
personal_access_tokens
表建立模型,並將expired_at
寫入fillable
屬性中。 - 在 Sanctum 的
authenticate
回撥方法中驗證expired_at
來判斷 token 是否過期。
如果你的專案還沒有使用過 Sanctum
:
composer require laravel/sanctum
釋出 Sanctum 的配置檔案和遷移檔案:
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
Step1
來新增欄位到遷移檔案中,開啟 migrations/create_personal_access_token
,將
$table->timestamp('expired_at')->nullable();
新增到 $table->timestamp('last_used_at')->nullable();
之後。
$table->id();
$table->morphs('tokenable');
$table->string('name');
$table->string('token', 64)->unique();
$table->text('abilities')->nullable();
$table->timestamp('last_used_at')->nullable();
$table->timestamp('expired_at')->nullable(); // 新增
$table->timestamps();
執行遷移:
php artisan migrate
Step2
在 User 模型中使用 trait HasApiTokens
,然後來複寫一下 其中的 createToken
方法。
// Models/User.php
public function createToken(string $name, array $abilities = ['*'], $expired_at = 3)
{
$token = $this->tokens()->create([
'name' => $name,
'token' => hash('sha256', $plainTextToken = Str::random(40)),
'abilities' => $abilities,
'expired_at' => now()->addHours($expired_at) // 新增這行,先給它預設有效3小時。
]);
return new NewAccessToken($token, $token->getKey().'|'.$plainTextToken);
}
Step3
為 Sanctum 建立模型
php artisan make:model PersonalAccessToken
新增 expired_at
到 fillable
屬性:
use Laravel\Sanctum\PersonalAccessToken as Model; // 這裡要注意模型的繼承,NewAccessToken 控制器中只接受 `Laravel\Sanctum\PersonalAccessToken` 模型。
class PersonalAccessToken extends Model
{
protected $casts = [
'abilities' => 'json',
'last_used_at' => 'datetime',
'expired_at' => 'datetime'
];
protected $fillable = [
'name',
'token',
'abilities',
'expired_at'
];
}
基於 Sanctum 的文件,如果你想要使用自定義的模型,需要在 providers/AuthServiceProvider.php
中註冊:
public function boot()
{
...
Sanctum::usePersonalAccessTokenModel(PersonalAccessToken::class);
}
Step5
最後來讓我們修改 Sanctum 的認證回撥邏輯,預設情況下,它只會計算 token 的雜湊值來確保它是有效的。
這是 Sanctum 驗證 token 的程式碼邏輯:
protected function isValidAccessToken($accessToken): bool
{
if (! $accessToken) {
return false;
}
$isValid =
(! $this->expiration || $accessToken->created_at->gt(now()->subMinutes($this->expiration)))
&& $this->hasValidProvider($accessToken->tokenable);
if (is_callable(Sanctum::$accessTokenAuthenticationCallback)) {
$isValid = (bool) (Sanctum::$accessTokenAuthenticationCallback)($accessToken, $isValid);
}
return $isValid;
}
注意這裡有一個判斷:
if(is_callable(Sanctum::$accessTokenAuthenticationCallback))
如果這個回撥方法存在,則 token 是否有效就取決於我們自定義的回撥。
再次開啟 providers/AuthServiceProvider.php
檔案,來註冊這個回撥
Sanctum::authenticateAccessTokensUsing(
static function (PersonalAccessToken $accessToken, bool $is_valid) {
// 自定義的驗證邏輯
}
);
鑑於我們新增了自定義回撥,會影響現有的已經頒發 token 的使用者,所以這裡我們來做一個判斷,如果 expired_at
欄位不為 null,我們就檢查它是否過期,否則就不進行回撥處理。
return $accessToken->expired_at ? $is_valid && !$accessToken->expired_at->isPast() : $is_valid;
來測試一下
建立驗證控制器
php artisan make:controller Api\AuthorizationController.php
public function store(Request $request)
{
if (!Auth::attempt($request->only(['email', 'password']))) {
abort(403);
}
$token = auth()->user()->createToken('api', ['*'], 5);
return response()->json([
'token' => $token->plainTextToken,
'expired_at' => $token->accessToken->expired_at
]);
}
為了方便測試,我們手動更改一下表中的過期時間,將它改成過去的時間
在 api.php
路由中新增:
Route::group([
'middleware' => [
'auth:sanctum'
]
],function(){
Route::get('test_token', function(){
return 'token有效';
});
});
帶 Bearer Token
來訪問 your_project.test/api/test_token
,下面我就不再演示了。
最後,token 的預設建立時間
上面的 createToken
方法中,我們預設先將 token
有效期設定為了 3 小時,下面我們來更改為 「如果沒有傳入有效期時,為它使用全域性配置」
開啟 config/sanctum.php
配置檔案,新增全域性配置:
'default_expiration' => env("SANCTUM_DEFAULT_EXPIRATION", 10080),
改寫 createToken
方法為:
public function createToken(string $name, array $abilities = ['*'], $expired_at = null)
{
$expired_at = $expired_at ?: config('sanctum.default_expiration');
$token = $this->tokens()->create([
'name' => $name,
'token' => hash('sha256', $plainTextToken = Str::random(40)),
'abilities' => $abilities,
'expired_at' => now()->addHours($expired_at)
]);
return new NewAccessToken($token, $token->getKey().'|'.$plainTextToken);
}
這裡需要注意的一點是,如果我們將過期時間使用以上方法時,就不要再為配置檔案新增以下兩個配置:
refresh_expiration
expiration
否則 Sanctum 會在我們進行回撥驗證之前來決定 token 是否過期。
總結
這個解決方案不只適用於 User
模型,它是通用的,只要使用以上方法,可以為任意需要驗證的表來新增自定義有效期的 token
。
enjoy
本作品採用《CC 協議》,轉載必須註明作者和本文連結