Laravel Sanctum 如何自定义每个 token 的过期时间
之前论坛中有人提问过 问答: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 协议》,转载必须注明作者和本文链接
推荐文章: