之前论坛中有人提问过: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); }
Step4
最后来让我们修改 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。