28人参与 • 2026-08-18 • Windows
在分布式系统中,令牌续期不是“把过期时间重置”这么简单,而是需要在安全性、用户体验与系统性能之间取得平衡。常见痛点包括:
续期策略设计需优先回答三个问题:
方案 | 安全性 | 用户体验 | 实现复杂度 | 适用场景 | 性能影响 |
|---|---|---|---|---|---|
单token基础版 | ★☆☆☆☆ | ★★☆☆☆ | ★☆☆☆☆ | 内部测试系统 | 低 |
单token+黑名单 | ★★☆☆☆ | ★★★☆☆ | ★★☆☆☆ | 低风险web应用 | 中 |
双token基础版 | ★★★☆☆ | ★★★★☆ | ★★★☆☆ | 常规web/app | 中 |
双token+三验证 | ★★★★★ | ★★★☆☆ | ★★★★☆ | 金融/支付系统 | 高 |
自动续期方案 | ★★★★☆ | ★★★★★ | ★★★★☆ | 高用户体验要求系统 | 中高 |
分布式环境增强 | ★★★★☆ | ★★★★☆ | ★★★★☆ | 多设备、跨服务系统 | 中 |
思路:仅使用 access token,不进行任何额外的续期或黑名单处理。当 token 过期后,用户需重新登录获取新的 token。
实现:直接签发一个有固定过期时间的 access token,在验证 token 时,仅检查其是否在有效期内。
适用场景:内部低安全要求的测试系统、短期活动页面、快速原型开发等对安全性和用户体验要求不高的场景。
<?php
class tokenservice
{
private const access_ttl = 1800; // 30 分钟
// 生成 access token
public function generatetoken(string $username): string
{
return jwtutil::encode([
'sub' => $username,
'iat' => time(),
'exp' => time() + self::access_ttl,
]);
}
// 验证 token 是否有效
public function validatetoken(string $token): bool
{
try {
$expiration = jwtutil::getexpiration($token);
return $expiration > time();
} catch (exception $e) {
// 若解析 token 失败,认为 token 无效
return false;
}
}
}思路:仅使用access token,续期时签发新令牌,并将旧令牌加入黑名单,黑名单 ttl 略大于令牌 ttl,避免并发刷新导致旧令牌仍可用。
适用:内部低风险系统、短期活动页、快速原型。
要点:

<?php
class tokenservice
{
private const access_ttl = 1800; // 30分钟
private const blacklist_ttl = 2100; // 35分钟
public function refresh(string $oldtoken): string
{
if ($this->isblacklisted($oldtoken)) {
throw new runtimeexception('token revoked', 401);
}
$payload = jwtutil::decode($oldtoken);
$sub = $payload['sub'] ?? null;
if (!$sub) {
throw new runtimeexception('invalid token', 401);
}
// 先拉黑旧令牌,再签发新令牌(减少并发窗口)
$this->addblacklist($oldtoken, self::blacklist_ttl);
return jwtutil::encode([
'sub' => $sub,
'iat' => time(),
'exp' => time() + self::access_ttl,
]);
}
private function isblacklisted(string $token): bool
{
return (bool) redis::get("blacklist:{$token}");
}
private function addblacklist(string $token, int $ttl): void
{
redis::setex("blacklist:{$token}", $ttl, '1');
}
}思路:登录签发access token(短期)与refresh token(长期);access 过期后,用 refresh 换取新 access。refresh 存于服务端(如 redis),便于撤销与管控。
适用:常规 web/app,安全性与体验均衡。
要点:

<?php
class tokenservice
{
private const access_ttl = 900; // 15分钟
private const refresh_ttl = 604800; // 7天
public function login(string $userid): array
{
$accesstoken = jwtutil::encode([
'sub' => $userid,
'iat' => time(),
'exp' => time() + self::access_ttl,
'type' => 'access',
]);
$refreshtoken = bin2hex(random_bytes(32));
redis::setex("refresh:{$refreshtoken}", self::refresh_ttl, $userid);
return compact('accesstoken', 'refreshtoken');
}
public function refresh(string $refreshtoken): string
{
$userid = redis::get("refresh:{$refreshtoken}");
if (!$userid) {
throw new runtimeexception('invalid or expired refresh token', 401);
}
// 方案a:撤销式刷新(推荐)
redis::del("refresh:{$refreshtoken}");
// 方案b:滑动续期(可选)
// redis::expire("refresh:{$refreshtoken}", self::refresh_ttl);
return jwtutil::encode([
'sub' => $userid,
'iat' => time(),
'exp' => time() + self::access_ttl,
'type' => 'access',
]);
}
public function logout(string $refreshtoken): void
{
redis::del("refresh:{$refreshtoken}");
}
}思路:在基础版上增加:
要点:

<?php
class tokenservice
{
private const access_ttl = 900;
private const refresh_ttl = 604800;
private const state_ttl = 300; // 5分钟
public function refreshwithstate(string $refreshtoken, string $clientstate, string $deviceid): array
{
$lockkey = "lock:refresh:{$refreshtoken}";
$statekey = "state:{$clientstate}";
// 分布式锁(setnx + ex 简化示例)
$acquired = redis::set($lockkey, 1, ['nx', 'ex' => 5]);
if (!$acquired) {
throw new runtimeexception('refresh in progress, try later', 429);
}
try {
// 1) 一次性 statetoken 校验
$stored = redis::get($statekey);
if (!$stored || $stored !== $deviceid) {
throw new runtimeexception('invalid state or device mismatch', 401);
}
redis::del($statekey);
// 2) refresh 令牌校验
$userid = redis::get("refresh:{$refreshtoken}");
if (!$userid) {
throw new runtimeexception('invalid or expired refresh token', 401);
}
// 3) 撤销式:删除旧 refreshtoken
redis::del("refresh:{$refreshtoken}");
// 4) 生成新令牌对
$newaccess = jwtutil::encode([
'sub' => $userid,
'iat' => time(),
'exp' => time() + self::access_ttl,
'type' => 'access',
]);
$newrefresh = bin2hex(random_bytes(32));
redis::setex("refresh:{$newrefresh}", self::refresh_ttl, $userid);
return compact('newaccess', 'newrefresh');
} finally {
redis::del($lockkey);
}
}
public function createrefreshstate(string $deviceid): string
{
$state = bin2hex(random_bytes(16));
redis::setex("state:{$state}", self::state_ttl, $deviceid);
return $state;
}
}思路:在网关/中间件或业务拦截器中检测令牌剩余有效期,低于阈值时签发新令牌并通过响应头返回,客户端无感替换。
适用:微服务、前后端分离、高并发系统。
要点:

<?php
// 网关/中间件示例(伪代码,可按 webman/laravel/swoole 适配)
class tokenrenewmiddleware
{
private const renew_threshold = 300; // 5分钟
public function handle($request, $next)
{
$token = $this->extracttoken($request);
if (!$token) {
return $next($request);
}
try {
$payload = jwtutil::decode($token);
} catch (exception $e) {
return $this->unauthorized('invalid token');
}
$remaining = $payload['exp'] - time();
if ($remaining > self::renew_threshold) {
return $next($request);
}
// 签发新令牌
$newtoken = jwtutil::encode([
'sub' => $payload['sub'],
'iat' => time(),
'exp' => time() + 900, // 15分钟
'type' => 'access',
]);
$response = $next($request);
$response->withheader('x-new-token', $newtoken);
return $response;
}
private function extracttoken($request): ?string
{
$auth = $request->header('authorization', '');
return str_starts_with($auth, 'bearer ') ? substr($auth, 7) : null;
}
private function unauthorized(string $msg)
{
return new response(401, ['content-type' => 'application/json'], json_encode(['error' => $msg]));
}
}思路:以用户+设备维度维护最新令牌,登录时使旧令牌失效(黑名单或缓存淘汰);跨服务通过本地快速校验 + 认证中心兜底提升性能与一致性。
要点:

<?php
class sessionservice
{
// 登录:单设备登录(踢旧)
public function login(string $userid, string $deviceid): string
{
$token = jwtutil::encode([
'sub' => $userid,
'iat' => time(),
'exp' => time() + 900,
'type' => 'access',
'device' => $deviceid,
]);
$key = "session:{$userid}:{$deviceid}";
$oldtoken = redis::get($key);
if ($oldtoken) {
// 使旧令牌失效(黑名单或缓存失效)
redis::setex("blacklist:{$oldtoken}", 2100, '1');
}
redis::setex($key, 900, $token);
return $token;
}
// 跨服务验证:本地快速校验 + 认证中心兜底
public function validateacrossservices(string $token): bool
{
try {
$payload = jwtutil::decode($token);
$key = "session:{$payload['sub']}:{$payload['device']}";
$current = redis::get($key);
return $current && hash_equals($current, $token);
} catch (exception $e) {
// 本地失败,调用认证中心兜底
return authcenterclient::validate($token);
}
}
}要点:
到此这篇关于详解多种主流token续期方案对比解析的文章就介绍到这了,更多相关token续期内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
您想发表意见!!点此发布评论
版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。
发表评论