如何在Laravel中基于jwt-auth生成并验证Refresh Token?
你当前代码里用auth()->attempt()生成refresh token的方式存在问题——这会生成和access token完全一致的JWT,无法独立管控失效逻辑,不符合refresh token的设计初衷。下面是一套完整的实现方案:
1. 创建Refresh Token数据表
首先生成迁移文件,用来持久化存储用户的refresh token:
php artisan make:migration create_refresh_tokens_table
修改迁移文件内容:
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; return new class extends Migration { public function up() { Schema::create('refresh_tokens', function (Blueprint $table) { $table->id(); $table->foreignId('user_id')->constrained()->onDelete('cascade'); $table->string('token', 100)->unique(); $table->timestamp('expires_at'); $table->timestamps(); }); } public function down() { Schema::dropIfExists('refresh_tokens'); } };
执行迁移:
php artisan migrate
2. 配置模型关联
在User模型中添加与refresh token的关联:
// app/Models/User.php public function refreshTokens() { return $this->hasMany(RefreshToken::class); }
创建RefreshToken模型:
php artisan make:model RefreshToken
模型内容如下:
// app/Models/RefreshToken.php namespace App\Models; use Illuminate\Database\Eloquent\Model; class RefreshToken extends Model { protected $fillable = ['token', 'expires_at']; }
3. 修改Login方法,生成并存储Refresh Token
更新AuthController的login方法,仅在用户提交remember-me为true时生成refresh token:
// app/Http/Controllers/AuthController.php use Illuminate\Support\Str; use App\Models\RefreshToken; public function login(Request $request) { $validator = Validator::make($request->all(), [ 'email' => 'required|email', 'password' => 'required|string|min:6', 'remember-me' => 'boolean|nullable', ]); if ($validator->fails()) { return response()->json($validator->errors(), 422); } if (!$token = auth()->attempt($validator->validated())) { return response()->json(['error' => 'Unauthorized'], 401); } $response = $this->createNewToken($token); // 仅当用户勾选「记住我」时生成refresh token if ($request->boolean('remember-me')) { // 生成随机字符串作为refresh token $refreshToken = Str::random(64); $expiresAt = now()->addMonths(6); // 存储哈希后的token到数据库(避免明文泄露风险) auth()->user()->refreshTokens()->create([ 'token' => hash('sha256', $refreshToken), 'expires_at' => $expiresAt, ]); // 设置HttpOnly Cookie $cookie = cookie('__refresh-token') ->withValue($refreshToken) ->withExpires($expiresAt->getTimestamp()) ->withSecure(config('app.env') === 'production') ->withHttpOnly(true) ->withSameSite('none'); $response->cookie($cookie); } return $response; }
4. 新增静默刷新接口
在AuthController中添加silentLogin方法,用于通过Cookie中的refresh token获取新的access token:
public function silentLogin(Request $request) { // 从Cookie中获取refresh token $refreshToken = $request->cookie('__refresh-token'); if (!$refreshToken) { return response()->json(['error' => 'No refresh token provided'], 401); } // 哈希后匹配数据库记录 $hashedToken = hash('sha256', $refreshToken); $tokenRecord = RefreshToken::where('token', $hashedToken)->first(); if (!$tokenRecord || $tokenRecord->expires_at->isPast()) { // 清除无效Cookie并返回错误 $response = response()->json(['error' => 'Invalid or expired refresh token'], 401); return $response->withoutCookie('__refresh-token'); } // 登录关联用户并生成新的access token auth()->login($tokenRecord->user); $newAccessToken = auth()->generateToken(); // 可选:滚动刷新refresh token,增强安全性 $newRefreshToken = Str::random(64); $newExpiresAt = now()->addMonths(6); $tokenRecord->update([ 'token' => hash('sha256', $newRefreshToken), 'expires_at' => $newExpiresAt, ]); // 更新Cookie $cookie = cookie('__refresh-token') ->withValue($newRefreshToken) ->withExpires($newExpiresAt->getTimestamp()) ->withSecure(config('app.env') === 'production') ->withHttpOnly(true) ->withSameSite('none'); return $this->createNewToken($newAccessToken)->cookie($cookie); }
在routes/api.php中添加接口路由:
Route::post('/silent-login', [AuthController::class, 'silentLogin']);
5. 完善Logout方法
更新logout方法,同时清除数据库中的refresh token和前端Cookie:
public function logout() { $refreshToken = request()->cookie('__refresh-token'); if ($refreshToken) { $hashedToken = hash('sha256', $refreshToken); RefreshToken::where('token', $hashedToken)->delete(); } auth()->logout(); return response()->json(['message' => 'User successfully signed out']) ->withoutCookie('__refresh-token'); }
前端调用逻辑
- 用户登录勾选「记住我」后,后端自动设置HttpOnly Cookie,前端无需手动存储refresh token,浏览器会在请求时自动携带。
- 当access token过期时,前端调用
/silent-login接口,浏览器自动带上Cookie中的refresh token,后端验证通过后返回新的access token,实现静默登录。
额外优化
- 生产环境必须开启
SecureCookie,确保仅通过HTTPS传输。 - 定期清理数据库中过期的refresh token,可通过任务调度实现:
// app/Console/Kernel.php protected function schedule(Schedule $schedule) { $schedule->call(function () { RefreshToken::where('expires_at', '<', now())->delete(); })->daily(); }
内容的提问来源于stack exchange,提问作者Bufer
相关产品推荐
相关产品推荐

