如何使用Laravel Sanctum生成刷新令牌及替换过期令牌
Laravel Sanctum 生成刷新令牌及令牌刷新方案
Laravel Sanctum 默认的 API 令牌没有内置刷新机制,需要我们自定义实现一套刷新令牌逻辑,以下是具体方案:
一、创建刷新令牌存储表
首先生成迁移文件来存储刷新令牌:
php artisan make:migration create_refresh_tokens_table
编辑迁移文件,添加必要字段:
public function up() { Schema::create('refresh_tokens', function (Blueprint $table) { $table->id(); $table->foreignId('user_id')->constrained()->onDelete('cascade'); $table->foreignId('access_token_id')->constrained('personal_access_tokens')->onDelete('cascade'); $table->string('token')->unique(); $table->timestamp('expires_at'); $table->timestamps(); }); }
执行迁移:
php artisan migrate
二、定义模型与关联
创建 RefreshToken 模型:
php artisan make:model RefreshToken
在模型中设置关联和可填充字段:
namespace App\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; class RefreshToken extends Model { protected $fillable = ['user_id', 'access_token_id', 'token', 'expires_at']; public function user(): BelongsTo { return $this->belongsTo(User::class); } public function accessToken(): BelongsTo { return $this->belongsTo(PersonalAccessToken::class); } }
在 User 模型中添加刷新令牌关联:
public function refreshTokens() { return $this->hasMany(RefreshToken::class); }
三、登录时同时生成访问令牌与刷新令牌
在登录接口中,验证用户凭据后,生成 Sanctum 访问令牌的同时创建对应的刷新令牌:
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Str; use App\Models\RefreshToken; public function login(Request $request) { $validated = $request->validate([ 'email' => 'required|email', 'password' => 'required', ]); if (!Auth::attempt($validated)) { return response()->json(['message' => '无效登录凭据'], 401); } $user = Auth::user(); // 生成1小时过期的访问令牌 $accessToken = $user->createToken('auth_token', expiresAt: now()->addHour()); // 生成刷新令牌并哈希存储(仅返回明文一次给前端) $refreshTokenPlain = Str::random(64); RefreshToken::create([ 'user_id' => $user->id, 'access_token_id' => $accessToken->accessToken->id, 'token' => hash('sha256', $refreshTokenPlain), 'expires_at' => now()->addDays(7), // 刷新令牌有效期设为7天 ]); return response()->json([ 'access_token' => $accessToken->plainTextToken, 'refresh_token' => $refreshTokenPlain, 'token_type' => 'Bearer', 'expires_at' => $accessToken->accessToken->expires_at->toDateTimeString(), ]); }
四、使用刷新令牌获取新访问令牌
创建刷新接口,验证刷新令牌的有效性后生成新的访问令牌:
use Illuminate\Support\Facades\Hash; use App\Models\RefreshToken; public function refresh(Request $request) { $validated = $request->validate([ 'refresh_token' => 'required|string', ]); $hashedToken = hash('sha256', $validated['refresh_token']); $refreshToken = RefreshToken::where('token', $hashedToken)->first(); // 检查刷新令牌是否存在或已过期 if (!$refreshToken || $refreshToken->expires_at->isPast()) { return response()->json(['message' => '刷新令牌无效或已过期'], 401); } $user = $refreshToken->user; // 注销旧的访问令牌 $refreshToken->accessToken->delete(); // 生成新的访问令牌 $newAccessToken = $user->createToken('auth_token', expiresAt: now()->addHour()); // 更新刷新令牌为新的(也可选择生成全新的刷新令牌) $newRefreshTokenPlain = Str::random(64); $refreshToken->update([ 'token' => hash('sha256', $newRefreshTokenPlain), 'access_token_id' => $newAccessToken->accessToken->id, 'expires_at' => now()->addDays(7), ]); return response()->json([ 'access_token' => $newAccessToken->plainTextToken, 'refresh_token' => $newRefreshTokenPlain, 'token_type' => 'Bearer', 'expires_at' => $newAccessToken->accessToken->expires_at->toDateTimeString(), ]); }
五、路由配置
在 routes/api.php 中添加对应的路由:
use App\Http\Controllers\AuthController; Route::post('/login', [AuthController::class, 'login']); Route::post('/refresh', [AuthController::class, 'refresh']);
六、关键注意事项
- 刷新令牌建议存储在HttpOnly、Secure Cookie中,避免XSS攻击窃取
- 始终哈希存储刷新令牌,数据库中不要保留明文
- 可添加刷新令牌的单次使用限制(验证后立即失效,生成新的),提升安全性
- 提供注销接口,允许用户删除所有关联的访问令牌和刷新令牌
内容的提问来源于stack exchange,提问作者user14773037
相关产品推荐
相关产品推荐

