广告位联系
返回顶部
分享到

多种主流Token续期方案对比介绍

相关其他 来源:互联网 作者:佚名 发布时间:2026-08-18 22:07:08 人浏览
摘要

一 、背景与核心问题 在分布式系统中,令牌续期不是把过期时间重置这么简单,而是需要在安全性、用户体验与系统性能之间取得平衡。常见痛点包括: 旧令牌在有效期内仍可使用,形成安

一 、背景与核心问题

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

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

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

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

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

方案

安全性

用户体验

实现复杂度

适用场景

性能影响

单Token基础版

★☆☆☆☆

★★☆☆☆

★☆☆☆☆

内部测试系统

单Token+黑名单

★★☆☆☆

★★★☆☆

★★☆☆☆

低风险Web应用

双Token基础版

★★★☆☆

★★★★☆

★★★☆☆

常规Web/APP

双Token+三验证

★★★★★

★★★☆☆

★★★★☆

金融/支付系统

自动续期方案

★★★★☆

★★★★★

★★★★☆

高用户体验要求系统

中高

分布式环境增强

★★★★☆

★★★★☆

★★★★☆

多设备、跨服务系统

三、不同方案与代码实现

3.1 单Token基础版

思路:仅使用 Access Token,不进行任何额外的续期或黑名单处理。当 Token 过期后,用户需重新登录获取新的 Token。
实现:直接签发一个有固定过期时间的 Access Token,在验证 Token 时,仅检查其是否在有效期内。
适用场景:内部低安全要求的测试系统、短期活动页面、快速原型开发等对安全性和用户体验要求不高的场景。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

<?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,实现立即失效。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

<?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秒。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

<?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 → 跳转登录;
  • 网关层统一治理,减少业务侵入。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

<?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 验签 + 黑名单校验,失败再调用 认证中心? 兜底,降低跨网开销。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

<?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 使用频率监控,异常触发告警与限流

版权声明 : 本文内容来源于互联网或用户自行发布贡献,该文观点仅代表原作者本人。本站仅提供信息存储空间服务和不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权, 违法违规的内容, 请发送邮件至2530232025#qq.cn(#换@)举报,一经查实,本站将立刻删除。
原文链接 :
相关文章
  • 本站所有内容来源于互联网或用户自行发布,本站仅提供信息存储空间服务,不拥有版权,不承担法律责任。如有侵犯您的权益,请您联系站长处理!
  • Copyright © 2017-2022 F11.CN All Rights Reserved. F11站长开发者网 版权所有 | 苏ICP备2022031554号-1 | 51LA统计