详解多种主流Token续期方案对比解析

 更新时间:2026年08月18日 09:23:34   作者:denglei.  
本文探讨分布式系统中令牌续期策略的设计与实现,针对安全、体验与性能的平衡问题,对比五种主流方案:单Token+黑名单、双Token基础版、双Token+三验证、自动续期及多设备会话管理,感兴趣的可以了解一下

一 、背景与核心问题

在分布式系统中,令牌续期不是“把过期时间重置”这么简单,而是需要在安全性用户体验系统性能之间取得平衡。常见痛点包括:

  • 旧令牌在有效期内仍可使用,形成安全黑洞;
  • 高并发场景下多个请求同时触发刷新,引发并发风暴与状态不一致;
  • 多设备、跨服务调用导致会话冲突与验证链路复杂。

续期策略设计需优先回答三个问题:

  • 何时续期:固定窗口、滑动窗口还是按需刷新;
  • 如何续期:单令牌还是双令牌(Access/Refresh),有状态还是无状态;
  • 如何安全防控:令牌撤销、黑名单、设备绑定、限频与密钥轮换等。

二、先给结论:几种方案对比表

方案

安全性

用户体验

实现复杂度

适用场景

性能影响

单Token基础版

★☆☆☆☆

★★☆☆☆

★☆☆☆☆

内部测试系统

单Token+黑名单

★★☆☆☆

★★★☆☆

★★☆☆☆

低风险Web应用

双Token基础版

★★★☆☆

★★★★☆

★★★☆☆

常规Web/APP

双Token+三验证

★★★★★

★★★☆☆

★★★★☆

金融/支付系统

自动续期方案

★★★★☆

★★★★★

★★★★☆

高用户体验要求系统

中高

分布式环境增强

★★★★☆

★★★★☆

★★★★☆

多设备、跨服务系统

三、不同方案与代码实现

3.1 单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;
        }
    }
}

3.2 单Token方案(黑名单优化)

思路:仅使用Access Token,续期时签发新令牌,并将旧令牌加入黑名单,黑名单 TTL 略大于令牌 TTL,避免并发刷新导致旧令牌仍可用。

适用:内部低风险系统、短期活动页、快速原型。

要点:

  • 黑名单 TTL 建议比 Access Token TTL 多 5分钟,覆盖网络延迟与时钟偏差;
  • 并发刷新时可能出现“旧令牌尚未加入黑名单”的短暂窗口,建议配合请求去重标记或分布式锁;
  • 登出/改密需立即写入黑名单,保证强制下线的实时性。

<?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');
    }
}

3.3 双Token方案(基础版)

思路:登录签发Access Token(短期)与Refresh Token(长期);Access 过期后,用 Refresh 换取新 Access。Refresh 存于服务端(如 Redis),便于撤销与管控。
适用:常规 Web/APP,安全性与体验均衡。
要点:

  • Refresh Token 建议存 Redis,支持撤销、滑动续期与次数限制;
  • 建议将 Refresh Token 通过 HttpOnly + Secure Cookie​ 下发,降低 XSS​ 风险;
  • 登出时删除 Refresh Token,实现立即失效。

<?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}");
    }
}

3.4 双Token方案(安全增强:三验证 + 并发控制)

思路:在基础版上增加:

  • 一次性 StateToken​ 防重放;
  • 设备绑定防被盗用;
  • 分布式锁避免并发风暴。
  • 适用:金融、支付、企业级应用。

要点:

  • 一次性 StateToken​ 解决并发刷新与重放攻击;
  • 设备绑定降低被盗用风险;
  • 分布式锁避免“并发风暴”,锁 TTL 建议 5–10秒。

<?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;
    }
}

3.5 自动续期方案(滑动窗口 + 网关/中间件)

思路:在网关/中间件或业务拦截器中检测令牌剩余有效期,低于阈值时签发新令牌并通过响应头返回,客户端无感替换。
适用:微服务、前后端分离、高并发系统。
要点:

  • 建议采用双阈值:剩余时间 ≤ 5分钟​ 或 ≤ 总有效期的30%​ 时触发续期;
  • 客户端需实现响应头监听与Token替换,失败兜底 401 → 跳转登录;
  • 网关层统一治理,减少业务侵入。

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

3.6 分布式环境增强(多设备与会话管理)

思路:以用户+设备维度维护最新令牌,登录时使旧令牌失效(黑名单或缓存淘汰);跨服务通过本地快速校验 + 认证中心兜底提升性能与一致性。
要点:

  • 单设备登录策略可减少会话冲突;如需多设备,建议记录设备列表并支持按设备强制下线;
  • 跨服务优先本地 JWT 验签 + 黑名单校验,失败再调用 认证中心​ 兜底,降低跨网开销。

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

四、 选型建议

  1. 原型/内测或低风险系统:优先用单Token+黑名单,实现成本最低;务必保证黑名单 TTL > Access Token TTL(建议多约 5 分钟),并限制刷新频率,避免被滥用。
  2. 常规业务(Web/APP)且需可控撤销:选择双Token基础版;将 Access Token ≤ 30 分钟、Refresh Token ≤ 7 天,Refresh 存 Redis 支持撤销与滑动续期;登出时删除 Refresh,保证可强制下线。
  3. 高安全场景(金融/支付/政企):采用双Token+三验证+并发锁;在签名校验基础上增加一次性 StateToken与设备绑定,配合分布式锁抵御并发风暴;必要时对 Refresh 实施次数限制与轮换策略。
  4. 强体验与统一治理(微服务/中台/SAAS):使用自动续期方案(网关/中间件统一拦截),以双阈值(如:剩余 ≤ 5 分钟​ 或 ≤ 总有效期的 30%)触发续期;响应头返回 X-New-Token,前端实现静默替换与失败兜底 401→登录。
  5. 多设备与会话治理:以用户+设备为维度维护最新令牌,登录时可选择单设备登录(踢旧)或多设备并存(可按设备强制下线);跨服务验证建议“本地快速验签 + 认证中心兜底”,降低跨网开销并保证一致性

要点:

  • 令牌时效基线:Access ≤ 30 分钟、Refresh ≤ 7 天;Refresh 配合刷新次数限制与滑动/撤销策略,避免长期可用与被盗用扩散。
  • 刷新阈值与策略:采用双阈值(绝对 + 相对)判断续期时机;网关/拦截器统一处理,减少业务侵入;前端必须实现静默更新与失败回退。
  • 并发与幂等:刷新端点加分布式锁(如按 jti/refreshToken 锁定),设置短TTL(5–10秒)避免阻塞;对同会话的并发刷新返回相同结果或排队提示,杜绝“并发风暴”。
  • 撤销与强制下线:登出/改密/风控立即使令牌失效(黑名单或删除 Refresh);黑名单 TTL 略大于 Access TTL;提供撤销接口与Refresh 使用频率监控,异常触发告警与限流

到此这篇关于详解多种主流Token续期方案对比解析的文章就介绍到这了,更多相关Token续期内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • win10安装Anaconda+tensorflow2.0-CPU+Pycharm的图文教程

    win10安装Anaconda+tensorflow2.0-CPU+Pycharm的图文教程

    本文通过图文并茂的形式给大家介绍了win10安装Anaconda+tensorflow2.0-CPU+Pycharm的教程,非常不错,具有一定的参考借鉴价值,需要的朋友可以参考下
    2019-12-12
  • java asp分析各种搜索引擎的关键字,自动识别url 中关键字的编码

    java asp分析各种搜索引擎的关键字,自动识别url 中关键字的编码

    网上也有一些代码,大部分都是通过输入的关键字来识别编码,并解码。但是搜索引擎得到的referer来源地址上的关键字是通过URLencode编码过的,而且各个网站的关键字Urlencode编码都不一样,gbk,utf-8,gb2312等等。
    2009-01-01
  • 分享4个最受欢迎的大数据可视化工具

    分享4个最受欢迎的大数据可视化工具

    大数据可视化是进行各种大数据分析解决的最重要组成部分之一。这篇文章主要介绍了4个最受欢迎的大数据可视化工具,需要的朋友可以参考下
    2019-06-06
  • VSCode SSH远程连接与删除的实现步骤

    VSCode SSH远程连接与删除的实现步骤

    本文主要介绍了VSCode SSH远程连接与删除的实现步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-07-07
  • xmind2022下载非试用超详细图文教程

    xmind2022下载非试用超详细图文教程

    这篇文章主要介绍了xmind2022下载非试用(超详细 图文预警),本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-10-10
  • Trae AI IDE的使用教程(全网最全)

    Trae AI IDE的使用教程(全网最全)

    字节推出TraeAIIDE专为中文开发者设计,集AI编码、Builder/Chat模式于一体,支持代码编辑、运行、样式改造及交互增强,提升开发效率与体验,感兴趣的可以了解一下
    2025-07-07
  • Git常用命令介绍

    Git常用命令介绍

    Git是一个开源的分布式版本控制系统,可以有效、高速地处理从很小到非常大的项目版本管理。这篇文章介绍了Git的常用命令,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-01-01
  • 编程界主流脚本编程语言的比较和选择

    编程界主流脚本编程语言的比较和选择

    这篇文章主要介绍了编程界主流脚本编程语言的比较和选择,本文分析了Unix/Linux Shell、Python/Jython、Ruby/JRuby、Perl、Groovy等脚本语言的优缺点,帮助您快速了解各大语言,需要的朋友可以参考下
    2015-05-05
  • WebStorm打开多个项目的三种方法总结

    WebStorm打开多个项目的三种方法总结

    WebStorm支持多项目管理,本文介绍了三种方法:使用项目导航面板、窗口分割和多个WebStorm实例,项目导航面板可以在同一窗口内管理多个项目,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2024-09-09
  • Typora使用方法

    Typora使用方法

    今天用Typora因为升级了一下,所以需要激活虽然勉强能用,但是老是弹出激活页面,很是苦恼,只能通过百度找方法进行解决一下了,下面跟随小编看下Typora使用方法,需要的朋友可以参考下
    2022-04-04

最新评论