Laravel Passport实现SPA令牌闲置过期及有效期刷新问题咨询
实现Laravel Passport访问令牌的滑动过期(每次使用刷新有效期)
嘿,我之前在做Laravel Passport + SPA的认证时,也碰到过这个滑动过期的需求——默认的固定过期时间确实不够灵活。下面是我实际用过的一套解决方案,亲测可行:
1. 自定义Passport令牌模型,添加「最后使用时间」字段
首先,我们需要追踪令牌的最后使用时间,所以得给Passport的oauth_access_tokens表加个字段,同时自定义令牌模型来支持这个字段。
生成迁移文件
运行Artisan命令创建迁移:
php artisan make:migration add_last_used_at_to_oauth_access_tokens_table
打开生成的迁移文件,添加last_used_at字段:
public function up() { Schema::table('oauth_access_tokens', function (Blueprint $table) { $table->timestamp('last_used_at')->nullable(); }); } public function down() { Schema::table('oauth_access_tokens', function (Blueprint $table) { $table->dropColumn('last_used_at'); }); }
执行迁移:
php artisan migrate
创建自定义AccessToken模型
在app/Models/Passport目录下创建AccessToken.php(如果目录不存在就新建):
<?php namespace App\Models\Passport; use Laravel\Passport\AccessToken as PassportAccessToken; class AccessToken extends PassportAccessToken { protected $fillable = [ 'name', 'scopes', 'revoked', 'last_used_at', ]; protected $dates = [ 'expires_at', 'last_used_at', ]; }
注册自定义模型
打开app/Providers/AuthServiceProvider.php,在boot方法里添加以下代码,告诉Passport使用我们的自定义模型:
use Laravel\Passport\Passport; use App\Models\Passport\AccessToken; public function boot() { $this->registerPolicies(); Passport::routes(); // 注册自定义AccessToken模型 Passport::useAccessTokenModel(AccessToken::class); // 这里可以设置令牌初始过期时间,比如2小时 Passport::tokensExpireIn(now()->addHours(2)); }
2. 创建中间件处理滑动过期逻辑
接下来我们需要一个中间件,每次请求时更新令牌的最后使用时间,并且在剩余有效期不足时生成新令牌。
生成中间件
运行命令:
php artisan make:middleware UpdateAccessTokenLastUsed
打开生成的中间件文件app/Http/Middleware/UpdateAccessTokenLastUsed.php,编写逻辑:
<?php namespace App\Http\Middleware; use Closure; use Illuminate\Support\Facades\Auth; use Carbon\Carbon; class UpdateAccessTokenLastUsed { public function handle($request, Closure $next) { $user = Auth::user(); if ($user && $token = $user->token()) { // 更新令牌最后使用时间 $token->last_used_at = Carbon::now(); $token->save(); // 检查剩余有效期(这里设置为30分钟,可根据需求调整) $threshold = env('ACCESS_TOKEN_SLIDING_THRESHOLD', 30); $remainingMinutes = $token->expires_at->diffInMinutes(Carbon::now()); if ($remainingMinutes < $threshold) { // 生成新令牌,复制原令牌的名称和权限范围 $newToken = $user->createToken($token->name, $token->scopes)->accessToken; // 吊销旧令牌(如果不需要立即吊销,可以注释这行) $token->revoke(); // 将新令牌通过响应头返回给前端 $response = $next($request); $response->headers->set('X-New-Access-Token', $newToken); return $response; } } return $next($request); } }
注册中间件
打开app/Http/Kernel.php,把这个中间件加到API中间件组里:
protected $middlewareGroups = [ 'api' => [ 'throttle:api', \Illuminate\Routing\Middleware\SubstituteBindings::class, // 添加我们的中间件 \App\Http\Middleware\UpdateAccessTokenLastUsed::class, ], ];
3. 前端处理新令牌
前端需要拦截响应,检查是否有X-New-Access-Token响应头,如果有,就替换本地存储的旧令牌,后续请求使用新令牌。
以Axios为例,添加响应拦截器:
// 假设你用localStorage存储令牌 axios.interceptors.response.use( (response) => { const newToken = response.headers['x-new-access-token']; if (newToken) { // 更新本地存储的令牌 localStorage.setItem('access_token', newToken); // 更新Axios默认请求头 axios.defaults.headers.common['Authorization'] = `Bearer ${newToken}`; } return response; }, (error) => { // 处理令牌过期等错误情况,比如跳转到登录页 if (error.response?.status === 401) { localStorage.removeItem('access_token'); window.location.href = '/login'; } return Promise.reject(error); } );
一些额外说明
- 如果你不想立即吊销旧令牌(允许旧令牌继续到原过期时间),可以注释掉中间件里的
$token->revoke();,但这样会存在多个有效令牌,需要权衡安全性。 - 滑动过期的阈值时间可以在
.env里配置ACCESS_TOKEN_SLIDING_THRESHOLD=30,方便灵活调整。 - 初始令牌过期时间可以在
AuthServiceProvider的boot方法里通过Passport::tokensExpireIn()设置。
内容的提问来源于stack exchange,提问作者no0by5
相关产品推荐
相关产品推荐

