ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Token过期判断与自动刷新:从JWT到双Token机制全解析

Token过期判断与自动刷新:从JWT到双Token机制全解析 面试里问到 Token很多人能背出 JWT 三个字但真正拉开差距的是两个具体问题怎么判断 Token 是否过期以及自动更新 Token 要怎么实现。这两个问题既是软件测试面试的必问题也是真实项目里最容易踩坑的地方。很多候选人把过期判断答成“看 exp 字段就够了”把自动更新答成“前端定时刷新”一听就知道没碰过完整项目。这篇文章按实际项目里落地的方式拆一遍附带测试用例设计和面试答题思路。适合准备软件测试面试的同学也适合刚接手接口鉴权模块的开发或测试。1. Token 为什么会过期把超时机制和失效机制分开理解1.1 过期是时间问题失效是状态问题判断 Token 是否过期之前先搞清楚一件事过期和失效不是同一个概念。过期是一个时间概念。服务端在签发 Token 时给它设置一个有效期常见做法是 JWT 里放一个 expexpiration time字段单位是 Unix 时间戳。有些 Token 还会带 iat 和 nbf分别表示签发时间和生效时间。如果当前时间已经大于 expToken 从时间上就已经过期了。失效是一个状态概念。Token 可能还没到过期时间但服务端已经让它不可用了。比如用户修改了密码系统把旧 Token 全部拉黑。管理员封禁了账号会话被强制终止。用户主动退出登录前端删掉本地 Token服务端把 jtiToken 唯一标识写入黑名单。单点登录场景下同一账号在另一个设备登录旧设备 Token 被踢下线。有一类常见面试追问就在这里前端拿到的 Token 明明还没到过期时间为什么接口返回 401真实原因往往不是过期而是服务端不再承认这个 Token。测试人员在设计用例时要同时覆盖时间过期和服务端失效两条链路。1.2 为什么不能把 Token 时间设得足够长有人会想既然判断过期和自动更新都麻烦那把 Access Token 的有效期设成 30 天甚至一年不就没这个问题了从安全角度看这不可取。Token 一旦泄露泄露窗口时间越长攻击者能盗用的时间就越长。权限变更后旧的 Token 如果长时间有效用户改了权限也无法即时生效。用户要求强制下线时服务端也难以及时把旧 Token 全部吊销。所以业界通用的思路是短期令牌负责访问长期令牌负责续期。Access Token 有效期短通常几分钟到几小时Refresh Token 有效期长通常几天到一个月。Access Token 过期后客户端用 Refresh Token 换新的 Access Token。这就是自动更新 Token 的基础设计。2. 判断 Token 是否过期前端预判、服务端终判、测试验证2.1 JWT 本地解码怎么判断JWT 的 payload 是 Base64 编码前端可以在不请求服务端的情况下解码出来。下面是一个手动解码的示例用于测试时可以快速看 expimport base64 import json def decode_jwt_payload(token): parts token.split(.) if len(parts) 2: return {} # JWT 的 payload 部分使用 Base64URL 编码长度可能不足 4 的倍数 padding * (4 - len(parts[1]) % 4) payload base64.urlsafe_b64decode(parts[1] padding) return json.loads(payload) payload decode_jwt_payload( eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiemhhbmciLCJleHAiOjE3MTY2MjQwMDB9.signature ) print(payload.get(exp))也可以借助 PyJWT 这类常见库只解 payload 不验签import jwt payload jwt.decode(token, options{verify_signature: False}) print(payload.get(exp))但这里要明确一个边界本地解码只能做提前预判不能作为安全结论。原因很简单前端时间可能不准。本地电脑时间被改错、没有自动同步都会导致判断偏差。JWT 只是 Token 的一种。很多项目的 Access Token 是随机字符串本地根本解不出 exp。服务端可能维护黑名单、踢人逻辑、权限变更记录这些状态本地并不知晓。所以前端最多拿 exp 做“提前提醒登录状态即将失效”之类的 UI 优化真正的过期判断必须以后端接口返回为准。2.2 服务端到底校验什么服务端收到请求后通常会做几件事校验签名确认 Token 是服务端自己签发的没有被篡改。校验时间字段确认当前时间是否在 nbf 和 exp 之间。校验 iss、aud 等声明确认 Token 的签发方和受众是否匹配。查询黑名单或 Redis 登录态确认 Token 是否已经被吊销。结合业务场景校验账号状态比如是否被封禁、是否被踢下线。用一个通用伪代码表示大概是这样def verify_token(token): try: payload jwt.decode(token, public_key, algorithms[RS256]) except jwt.ExpiredSignatureError: raise TokenExpiredError(expired) except jwt.InvalidTokenError: raise TokenInvalidError(invalid) # 有些项目会额外检查 jti 是否在黑名单里 if redis.exists(fblacklist:{payload[jti]}): raise TokenRevokedError(revoked) return payload这个流程说明一个问题前端看到“Token 没过期”服务端却可能因为黑名单、账号状态等原因拒绝。测试人员排查接口返回 401 时不能只看时间还要看服务端日志里具体是哪种错误。2.3 用 HTTP 状态码和错误信息定位过期还是失效判断 Token 是否过期最直接的痕迹来自接口响应。常见状态码401 Unauthorized未携带 Token、Token 过期、Token 非法、Token 被吊销都可能返回 401。403 ForbiddenToken 本身有效但没有权限访问这个资源。400 Bad Request请求参数或请求头格式不正确比如 Authorization 头没有带 Bearer 前缀。遇到 401 时优先看响应头和响应体。很多服务端会在响应头里写WWW-Authenticate: Bearer errorinvalid_token, error_descriptionThe access token expired响应体里也可能有 error_code 或 message比如{ code: 40101, message: token expired }这里要提醒一个容易误判的场景如果看到类似token exchange failed: token endpoint returned status 403 forbidden: country, region, or territory not supported的报错这类问题通常是账号归属地与当前访问区域不一致或者是网络出口地址发生了变化属于访问控制问题不是 Token 过期。测试时不要把这类问题当成刷新逻辑的 bug要按网络和账号方向排查。3. 自动更新 Token双 Token 机制和刷新链路3.1 Access Token 和 Refresh Token 怎么分工自动更新 Token 的前提是有一套双 Token 机制。两者的关系用一张表说明维度Access TokenRefresh Token有效期短常见 15 分钟到 2 小时长常见 7 天到 30 天使用场景每次接口请求都携带只用于刷新接口存储位置前端内存、localStorage、Cookie 等需要更高安全级别通常放 HttpOnly Cookie 或安全存储泄露影响短时间内可以冒用身份泄露后可以长期换取新 Access Token服务端状态JWT 场景下可以无状态通常需要记录、验证、轮换Refresh Token 的设计原则是“低频使用、高安全要求”。它不应该出现在普通接口请求里也不应该被业务代码随意读取。3.2 刷新流程一次走通自动更新的完整链路可以拆成六步客户端携带 Access Token 发起业务请求。服务端校验发现 Access Token 过期返回 401。客户端拦截到 401不再直接抛给用户而是先调用刷新接口。刷新接口校验 Refresh Token如果有效就签发新的 Access Token有些实现还会同时签发新的 Refresh Token。客户端保存新 Token。客户端重放刚才失败的原始请求。刷新接口的请求体通常类似于这样具体字段以项目实际协议为准{ refreshToken: xxxxxxxx, grantType: refresh_token }刷新接口的响应通常长这样{ accessToken: new-access-token, refreshToken: new-refresh-token, expiresIn: 3600 }这里最重要的一点是自动刷新不能散落在每个业务请求里自己写应该由统一的 HTTP 拦截器处理。前端可以在 axios 拦截器、后端可以写中间件统一识别 401统一调用刷新逻辑统一重放请求。否则写十个接口就有十份重复代码出了问题非常难排查。3.3 Refresh Token 的安全存储和轮换Refresh Token 比 Access Token 更敏感存储位置要单独考虑。放在 localStorage 里容易被 XSS 脚本读取放进 HttpOnly Cookie 能降低 XSS 风险但要注意 CSRF 防护。放到前端内存里则刷新页面后丢失用户会频繁重新登录。每个方案都有取舍面试时可以按项目场景说明。更关键的是 Refresh Token 轮换。每次刷新成功后服务端应该签发一个新的 Refresh Token同时让旧 Refresh Token 失效。这样即使旧 Refresh Token 在某个环节泄露攻击者也没法长期复用。服务端还要做重用检测如果发现一个已经被轮换过的旧 Refresh Token 再次出现在刷新请求里说明这个 Token 可能已经泄露应该把这个会话下的所有 Token 全部作废强制用户重新登录。4. 并发刷新和请求重放最容易翻车的地方4.1 多个 401 同时打过来会发生什么假设用户在页面上停了一段时间Access Token 已经过期。此时用户突然点击某个操作前端一下子发出五个请求。这五个请求都会收到 401。如果每个请求都各自去调刷新接口会发生两个问题一是刷新请求被调用五次浪费网络资源。二是如果 Refresh Token 每次刷新都会轮换五次刷新里只有第一次能成功后面四次可能拿到已经失效的旧 Refresh Token直接导致用户被强制下线。这个坑在真实项目里太常见了。所以自动更新 Token 不仅仅是一个接口逻辑更是一个并发控制问题。4.2 用一个刷新标志把并发请求挂起来正确做法是第一次收到 401 时开始刷新后续请求进入待处理队列等刷新完成后再批量重放。用一个前端伪代码说明思路let isRefreshing false; let pendingQueue []; async function requestWithToken(config) { const res await doRequest(config); if (res.status ! 401) { return res; } // 已经有刷新任务在进行暂时挂起 if (isRefreshing) { return new Promise((resolve, reject) { pendingQueue.push({ config, resolve, reject }); }); } isRefreshing true; try { const newToken await refreshToken(); // 只调用一次刷新接口 updateToken(newToken); // 重放等待中的请求 pendingQueue.forEach((item) { item.resolve(doRequest(item.config)); }); pendingQueue []; // 重放当前请求 return doRequest(config); } catch (e) { // 刷新失败所有等待中的请求一起失败 pendingQueue.forEach((item) { item.reject(e); }); pendingQueue []; redirectToLogin(); throw e; } finally { isRefreshing false; } }这个思路在不同语言里都可以套用。后端中间件也可以用一个全局刷新状态和一个等待队列实现类似的效果。重点不是代码抄不抄而是要让并发请求统一等待同一个刷新结果。4.3 重放请求要保留完整上下文重放请求不是简单再发一次。原请求里的 method、url、headers、body、超时时间、取消信号都要一起保留。有些 body 类型只能读取一次比如流式请求或 FormData如果第一次发送时读过了重放时可能拿不到数据。这种情况下要在请求发出前先缓存一份副本。另外重放次数要有限制。正常情况一次 401 触发一次刷新刷新成功重放一次就够了。如果重放后仍然 401说明新的 Access Token 可能也有问题或者权限已经被收回这时候要直接失败不能进入无限重试循环。5. 测试人员怎么设计 Token 场景用例5.1 从正常链路到异常链路的用例清单软件测试面试问到 Token本质上是在问你能不能把异常场景设计完整。下面是一份可以直接参考的用例清单用例场景操作方式预期结果Access Token 未过期正常登录后立即请求接口请求成功不出现刷新请求Access Token 已过期等待 Token 过期或手动构造过期 Token返回 401自动触发刷新原请求重放成功Refresh Token 已过期使用过期 Refresh Token 刷新刷新失败跳转登录页刷新期间多个请求并发同时发起多个接口请求只调用一次刷新接口所有请求最终成功刷新期间新请求进入刷新尚未完成时再发新请求新请求排队刷新完成后继续刷新接口网络异常断网或制造超时不无限重试提示用户保留登录态或跳登录旧 Refresh Token 被再次使用刷新成功后使用旧 Refresh Token服务端拒绝视安全策略决定是否踢登录多端登录同一账号两个设备同时使用不互相干扰或按业务规则强制下线服务端时间偏差修改服务端时间造成偏移能容忍短时误差不误杀正常请求权限变更后管理员修改用户角色后请求接口新权限立即生效或按策略过期生效这些用例能覆盖自动更新 Token 的主要风险点。面试时能说出其中五六个已经能证明你理解的不只是概念。5.2 怎么在测试环境模拟 Token 过期测试环境模拟过期有几个常见方法后端配置短 Token 有效期比如把 Access Token 设成 60 秒方便快速验证。用接口工具生成一个 exp 已经过去的 JWT替换到请求头里。在登录态存储里把当前用户的会话删除模拟服务端失效。如果系统支持直接调用内部接口或数据库操作把 Token 加入黑名单。有一点容易踩坑如果不调整服务端时间手动改 JWT 的 exp 也不一定有效因为服务端校验用的是服务器时间。反过来如果测试时发现“Token 明明没过期接口却说过期”先看服务端时间和本地时间是否一致。很多项目在虚拟机、容器环境里时间漂移会导致 Token 校验结果不稳定。测试机器最好开启时间自动同步服务端则要单独确认 NTP 配置。5.3 用日志和抓包判断刷新是否成功判断自动更新是否生效不能只看最终接口成功没有还要看请求链路。用接口测试工具或抓包工具观察第一个业务请求返回 401。紧接着出现一个刷新请求请求体里带 refreshToken。刷新请求返回 200响应体里有新的 accessToken。原来的业务请求被重放这次返回 200。如果看到 401 后没有刷新请求说明前端没有拦截或没有走统一刷新逻辑。如果看到多个刷新请求说明并发控制没有生效。如果刷新请求成功但业务请求没有重放说明拦截器拿到了新 Token 之后没有继续执行原请求。服务端日志要关注这几个关键词token expired、refresh token invalid、refresh success、token rotated。配合请求日志基本可以定位是前端问题、后端问题还是网络问题。6. 面试答题参考和常见追问6.1 先答清楚两层判断面试官问“如何判断 Token 是否过期”时一个稳妥的回答顺序是先回答本地层JWT 可以在前端解码看 exp不透明 Token 则无法本地判断。但本地判断只能做提前预警不能作为最终结论因为服务端可能还有黑名单和状态校验。再回答服务端层最终以服务端校验为准。服务端会验证签名、时间、jti、黑名单和账号状态。过期时返回 401无效或吊销也返回 401但响应头和响应体会带有具体错误信息。这样回答面试官能听出你是从实际项目出发而不是背了一个“看 exp”的结论。6.2 自动更新 Token 的标准回答骨架自动更新 Token 可以按四层来说第一层是双 Token 机制Access Token 负责请求Refresh Token 负责续期。Access Token 设置短有效期Refresh Token 设置长有效期刷新成功后进行 Refresh Token 轮换。第二层是统一拦截通过 HTTP 拦截器或中间件统一监听 401不让业务代码各自处理。第三层是并发控制刷新过程中使用队列或复用同一个刷新 Promise避免多个请求重复刷新。第四层是失败兜底刷新接口返回 401、超时或网络异常时清除登录态跳转登录页设置最大重试次数不无限循环。这套骨架覆盖了机制、实现、并发、安全四个维度面试时可以按这个顺序展开。6.3 常见追问和应对思路追问一前端定时器判断 Token 过期可以吗可以但只能做 UI 层的提前感知不能替代服务端校验。比如提前一分钟提示用户登录即将失效。而且用户在后台标签页时定时器可能被浏览器节流或挂起所以最终还是要靠 401 触发刷新。追问二如果 Refresh Token 也被盗了怎么办从安全策略上应对Refresh Token 轮换旧 Token 重用即视为异常绑定设备信息或指纹检测到异常 IP 后要求重新登录缩短 Refresh Token 有效期设置合理的安全阈值。追问三刷新接口本身也返回 401 怎么办说明 Refresh Token 已经无效或者被轮换。此时不应继续重试应该清理本地登录态引导用户回到登录页。这里还要注意刷新接口返回的 401 不能被拦截器再次触发刷新否则会死循环通常需要给刷新接口单独设置一个跳过逻辑。追问四后端如何实现双 Token 发布登录成功后签发两个 TokenAccess Token 短时间有效Refresh Token 保存到 Redis 或数据库并记录过期时间。收到刷新请求后先从存储里查 Refresh Token校验成功后签发新的 Access Token 和新的 Refresh Token同时删除旧记录。如果 Refresh Token 在黑名单里说明可能被重用需要做安全处理。追问五测试时怎么验证自动刷新只调用了一次可以做并发接口测试同时发送五个请求观察抓包结果刷新接口只能出现一次。如果出现多次说明并发控制有问题。这个用例在真实项目里非常值得写进回归测试。自动更新 Token 这套链路真正落地时最该盯住的不是概念而是并发刷新、刷新失败兜底和 Refresh Token 轮换。面试时能把判断逻辑说清楚是基础能把并发刷新和失败处理讲明白才是加分项。如果正在准备面试建议自己动手画一遍请求时序图再用接口工具模拟一次过期 Token很快就能形成自己的回答框架。
返回列表