3. Token 生命周期管理:Access Token 与 Refresh Token 的定义、作用、过期策略

好,咱们来聊聊 Token 生命周期管理。说实话,这是整个身份校验方案里最容易出问题的地方。我见过不少项目,Access Token 设个 7 天过期,结果泄露了,整个系统跟没穿裤子一样。也见过 Refresh Token 设计得太复杂,用户频繁被踢下线,体验极差。

说白了,Token 生命周期管理就是解决一个问题:如何在安全性和用户体验之间找到平衡点。我个人习惯把这个问题拆成三个部分来理解:Access Token 是什么、Refresh Token 是什么、以及它们各自的过期策略怎么定。

3.1 Access Token:短命的通行证

Access Token,你可以把它想象成一张酒店房卡。你拿着它,就能开门、进健身房、用早餐。但问题是,这张房卡如果丢了,别人捡到也能干同样的事。

定义与作用

Access Token 是一个短期有效的凭证,客户端每次请求资源时都要带上它。服务器验证通过后,才会返回数据。它的核心作用就两个:

  • 身份认证:证明“我是谁”
  • 授权访问:证明“我能干什么”

过期策略

这里有个关键点:Access Token 必须短命。为什么?因为它在网络中传输频繁,被截获的风险最高。

场景 推荐过期时间 说明
Web 应用 15-30 分钟 配合 HttpOnly Cookie,安全性较高
移动端 App 1-2 小时 网络环境相对可控,可适当放宽
高安全场景(金融、医疗) 5-10 分钟 宁可频繁刷新,也不能泄露
内部系统 2-4 小时 内网环境,风险较低
我的经验: 我曾经把一个内部系统的 Access Token 设成 8 小时过期,结果有次员工电脑被植入木马,Token 被偷了。黑客拿着这个 Token 在 8 小时内为所欲为。从那以后,我所有项目的 Access Token 都不超过 1 小时。

3.2 Refresh Token:长命的保险箱钥匙

Refresh Token 就不一样了。它是一把保险箱钥匙,平时不用,只有在你需要换新房卡的时候才拿出来用一次。

定义与作用

Refresh Token 是一个长期有效的凭证,专门用来获取新的 Access Token。它不直接访问资源,所以即使泄露,攻击者也只能用它来刷新 Token,而不能直接获取数据。

它的核心作用:

  • 延长会话:用户不需要频繁登录
  • 降低风险:即使 Access Token 泄露,攻击者也只能在短时间内作恶
  • 支持离线刷新:移动端 App 可以在后台静默刷新

过期策略

Refresh Token 的过期时间要长得多,但也不是越长越好。

场景 推荐过期时间 说明
Web 应用 7-14 天 配合滑动过期策略
移动端 App 30-90 天 用户习惯长时间不退出
高安全场景 1-3 天 频繁验证用户身份
记住我功能 30 天 需要用户主动勾选
注意: Refresh Token 绝对不能存储在客户端 localStorage 或 sessionStorage 中。我见过有人把 Refresh Token 明文存在 localStorage 里,结果 XSS 攻击直接拿走了所有用户的长期凭证。正确的做法是存储在 HttpOnly Cookie 中,或者使用移动端的 Keychain/Keystore。

3.3 核心流程:Access Token 与 Refresh Token 的协作

这两个 Token 是怎么配合工作的?我画了一张图,你看完就明白了。

Access Token 与 Refresh Token 协作流程 Access Token 与 Refresh Token 协作流程 客户端 认证服务器 资源服务器 ① 登录请求 ② 返回 AT + RT ③ 携带 Access Token 请求资源 ④ 返回资源数据 AT 过期 ⑤ 携带 Refresh Token 刷新 ⑥ 返回新的 Access Token ⑦ 使用新 AT 重新请求资源 ⑧ 返回资源数据 说明:AT = Access Token,RT = Refresh Token。步骤 ③-④ 是正常流程,步骤 ⑤-⑧ 是刷新流程。

流程其实很简单:

  1. 用户登录,服务器返回 Access Token 和 Refresh Token
  2. 客户端用 Access Token 请求资源
  3. Access Token 过期后,客户端用 Refresh Token 去换新的 Access Token
  4. 服务器验证 Refresh Token 有效,返回新的 Access Token
  5. 客户端用新 Token 继续请求资源

3.4 滑动过期策略:让用户体验更丝滑

嗯,这里要重点说一下滑动过期策略。你想想看,如果 Refresh Token 固定 7 天过期,用户在第 6 天登录,第 7 天还在用,结果突然被踢下线了。这体验多糟糕?

什么是滑动过期?

滑动过期就是:每次用户刷新 Token 时,Refresh Token 的过期时间自动延长。比如用户在第 6 天刷新了,那 Refresh Token 的过期时间就从第 7 天延长到第 13 天。

核心逻辑:

// 伪代码示例
function refreshToken(oldRefreshToken) {
    // 验证旧 Refresh Token
    if (!isValid(oldRefreshToken)) {
        return error("Token 无效");
    }
    
    // 生成新的 Access Token
    const newAccessToken = generateAccessToken(userId);
    
    // 滑动过期:重新计算过期时间
    const newRefreshToken = generateRefreshToken(userId, {
        expiresIn: 7 * 24 * 60 * 60 * 1000 // 从当前时间开始,再延长 7 天
    });
    
    return { accessToken: newAccessToken, refreshToken: newRefreshToken };
}
我的建议: 滑动过期策略一定要设置一个最大有效期。比如最长 30 天,超过这个时间,不管你怎么刷新,都必须重新登录。这样做的好处是:即使用户的 Refresh Token 被偷了,攻击者也只能在 30 天内作恶,而不是无限期地使用。

3.5 避坑指南:我踩过的那些坑

做 Token 生命周期管理,有几个坑我是一定要提醒你的:

  • Refresh Token 轮换:每次刷新时,旧的 Refresh Token 必须立即失效。我曾经见过一个系统,刷新后旧的 Refresh Token 还能用,结果攻击者拿着偷来的 Token 反复刷新,生生不息。
  • 并发刷新问题:如果客户端同时发起多个请求,每个请求都发现 Access Token 过期了,然后同时去刷新。这时候怎么办?我的做法是:加一个锁,只允许一个刷新请求执行,其他的等待结果。
  • Token 撤销:用户修改密码或账号被盗后,必须能立即撤销所有 Token。我习惯在 Redis 里维护一个黑名单,每次验证 Token 时先查一下黑名单。
我曾经踩过的坑: 有次我忘了给 Refresh Token 设置轮换机制。结果一个用户的 Token 被偷了,攻击者每隔几分钟就刷新一次,持续了整整一周。等我发现时,这个用户的账号已经被用来爬取了大量数据。从那以后,我每次刷新都会生成全新的 Refresh Token,并且立即让旧的失效。

3.6 总结:记住这三条原则

好了,关于 Token 生命周期管理,你只要记住这三条原则:

  1. Access Token 要短命:15 分钟到 1 小时,视场景而定
  2. Refresh Token 要长命但有限:7 天到 30 天,配合滑动过期
  3. 轮换和撤销不能少:每次刷新换新 Token,随时能撤销

做到这三点,你的身份校验方案就基本稳了。剩下的就是根据业务场景微调过期时间,这个需要你在实际项目中慢慢摸索。