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

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

  1. 在 Sanctum 迁移文件中添加 expired_at 字段。
  2. 覆写 HasApiToken 中的 createToken 方法。
  3. personal_access_tokens 表创建模型,并将 expired_at 写入 fillable 属性中。
  4. 在 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_atfillable 属性:

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

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

enjoy :tada:

本作品采用《CC 协议》,转载必须注明作者和本文链接
悲观者永远正确,乐观者永远前行。
本帖由系统于 3年前 自动加精
Image
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
讨论数量: 17
chowjiawei

还有一种方式 很好用 并且可以做主题的 切换 那就是 config 修改配置文件 临时生效 只在本次失效

3年前 评论
Image MArtian (楼主) 3年前
Image chowjiawei (作者) 3年前
Image MArtian (楼主) 3年前

这包唯一蛋疼的地方就是每次请求都会有三条sql。。

3年前 评论
Image zwping 1年前

什么时候不对数据库操作我在使用。不然我是不会使用这个包了,原本jwt的优势中就有减少数据库操作的优势。有私密数据可以多次加密也可以尽量保证数据安全。一般服务器secret不泄露的情况下都是安全的。

3年前 评论

没想到这个人就是我 :see_no_evil:

3年前 评论
Image MArtian (楼主) 3年前

在 step 2 的时候,User 模型是不是还得重写 tokens()方法呢?不然在这张表 personal_access_tokens 中的过期时间就无法记录了。

    .
    .
    .
    /**
     * Get the access tokens that belong to model.
     *
     * @return \Illuminate\Database\Eloquent\Relations\MorphMany
     */
    public function tokens(): \Illuminate\Database\Eloquent\Relations\MorphMany
    {
        return $this->morphMany(PersonalAccessToken::class, 'tokenable');
    }
3年前 评论
Image MArtian (楼主) 3年前
Image pretendtrue (作者) 3年前

我连统一配置的过期时间好像都不行

3年前 评论
Image MArtian (楼主) 3年前

最新版的已经支持,验证过期时间了,按照你的步骤应该只需要覆写,模型中的createToken方法灵活设置过期时间就可以了,大佬看看是不是这样的 file

1年前 评论
Image gxcnvip 1年前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!