Laravel Sanctum自定义每个token的过期时间方法

发布时间:2022-5-12 10:49

之前论坛中有人提问过:Sanctum 没办法手动设置过期时间吧?如果要定义 Sanctum token 的过期时间,可以在 config/sanctum.php 中来统一定义。

'expiration'  =>  env("SANCTUM_TTL",  10080), 
'refresh_expiration'  =>  env("SANCTUM_REFRESH_TTL",  43200),

这样的配置 token 的有效期是全局生效的,例如:

token1token2token3
120m120m120m

但如果我们想要以下的方式呢?

token1token2token3
10m60m70m

要这么做也很简单,思路如下:

  • 在 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
    ]);
}

Laravel Sanctum 如何自定义每个 token 的过期时间

为了方便测试,我们手动更改一下表中的过期时间,将它改成过去的时间

Laravel Sanctum 如何自定义每个 token 的过期时间

在 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。

 

 

GoLang与Java各自生成grpc代码流程介绍 生活杂谈

GoLang与Java各自生成grpc代码流程介绍

1.背景: 由于公司的日志系统使用的是plumelog,最近生产环境老是报 jedis连接池不够,导致丢失日志,而且服务老是重启,怀疑跟日志系统有关,于是自己改造plumelog,使用go grpc...
MySQL同步数据Replication的实现步骤 生活杂谈

MySQL同步数据Replication的实现步骤

MySQL提供了Replication功能,可以实现将一个数据库的数据同步到多台其他数据库。前者通常称之为主库(master),后者则被称从库(slave)。MySQL复制过程采用异步方式,但延时非常...