返回文章列表
JWT 滑动续期:让登录状态自然延续
2026年6月6日 19:3922 分钟阅读lemon

JWT 滑动续期:让登录状态自然延续

#登录

固定过期时间是绝对的,用户的使用是连续的。一个活跃用户不该因为时钟走到某个刻度而被踢出。

问题

最常见的 JWT Session 管理方式:签发一个 Token,设个固定过期时间,到期重新登录。

看似合理,但用户体验上有个尴尬场景——用户连续一周每天都来访问,第六天晚上还在操作,第二天早上回来,Session 过期了,被迫重新登录。

明明昨天还在用,凭什么今天就要重新认证?

本质矛盾:过期时间是硬性的,而人的行为是连续的。 一个正在活跃使用的 Session 不应该被时钟强制终止。

目标

一条朴素的规则:

七天内有过访问的,保持登录;超过七天没来的,自然过期。

不是"七天硬过期",而是"七天滑动窗口"——每一次访问都把窗口往后推。

方案对比

方案做法优劣
长过期 + 不续签JWT 直接设 30d简单粗暴,但一旦签发就无法收回,安全性差
双 Token(Access + Refresh)短期 Access + 长期 Refresh,客户端主动换 Token安全性好,但复杂度陡增——需要 Refresh Token 存储、额外 API 端点、客户端换 Token 逻辑
滑动续期服务端在请求链路上检测剩余有效期,临近过期时静默续签简单,无额外存储,天然实现滚动窗口,对客户端完全透明

滑动续期适合中小规模站点——实现成本低、行为符合直觉、7 天窗口本身就限制了风险周期。

核心概念

滑动续期的逻辑只有一句话:

当 JWT 剩余有效期不足总时长的一半时,静默签发新 Token 写回 Cookie。

为什么不等到最后几小时?

  • 续期和过期在时间上太近,用户断网或关浏览器后下次回来可能刚好越过那条线
  • 一半是常见实践(值可以调),逻辑不变:越早续,越不容易意外过期

时间线示意

假设 Token 总有效期 7 天,续期阈值 3.5 天:

Day 0   登录,签发 JWT(exp = Day 7)
Day 1   访问,剩余 6d > 3.5d → 不续期
Day 2   访问,剩余 5d > 3.5d → 不续期
Day 3   访问,剩余 4d > 3.5d → 不续期
Day 4   访问,剩余 3d < 3.5d → ✅ 续期,新 exp = Day 11
Day 5   访问,剩余 6d > 3.5d → 不续期
...
Day 10  访问,剩余 1d < 3.5d → ✅ 续期,新 exp = Day 17

如果 Day 4 之后再也不访问:
Day 11  → JWT 过期 → 下次访问需要重新登录

效果:活跃用户的 Session 永不过期;不活跃用户自然失效。

实现架构

滑动续期不需要客户端做任何改动——续期发生在服务端的请求拦截层(Middleware / Filter / Interceptor),对客户端完全透明。

请求 → Middleware 解析 Cookie → 检查剩余有效期 → 不足阈值?静默续签 → 继续处理

常量定义

SESSION_DURATION = 7d       // JWT 总有效期
RENEW_THRESHOLD = 3.5d      // 续期触发阈值(总时长的一半)

续期逻辑(伪代码)

function middleware(request) {
  const token = request.cookies.get('session');

  let payload = null;
  let newToken = null;
  let shouldClear = false;

  if (token) {
    try {
      payload = verifyJWT(token);
      const remaining = payload.exp - now();
      if (remaining > 0 && remaining < RENEW_THRESHOLD) {
        // 剩余不足阈值,静默续期
        newToken = signJWT({ user: payload.user });
      }
    } catch {
      // Token 已过期或被篡改,清除 Cookie
      shouldClear = true;
    }
  }

  // 路由保护逻辑(检查 payload 是否存在、角色是否正确等)
  // ...

  const response = next();
  if (newToken) {
    response.cookies.set('session', newToken, {
      expires: SESSION_DURATION,
      httpOnly: true,
      secure: true,
    });
  } else if (shouldClear) {
    response.cookies.set('session', '', { expires: 0 });
  }

  return response;
}

关键设计点:

  1. 先解析,再判断,最后统一写入响应——避免解析失败时 Cookie 操作被丢弃(重定向场景下需要特别注意)
  2. 过期 Token 自动清除——不留无效 Cookie 在浏览器里,下次请求不会带着一个注定失败的 Token
  3. 续期不影响请求处理——即使触发续期,当前请求照常处理,新 Token 只是顺便写在响应头里

Cookie 的有效期必须覆盖 JWT 的有效期。否则会出现:JWT 还有效,但浏览器已经不发 Cookie 了——等于白签。

建议两者设相同时长,保持一致。

路由覆盖范围

续期需要覆盖所有用户可能访问的路径——页面导航、API 调用、静态资源除外。

matcher = ['/((?!_next/static|_next/image|favicon.ico).*)'];

只有覆盖全站,才能确保"任何访问都续期"。如果 Matcher 只覆盖 /admin 等少数路径,普通页面访问不会触发续期,Session 可能比预期更早过期。

和 Access + Refresh 双 Token 的对比

维度滑动续期Access + Refresh
额外请求0(Middleware 透明处理)每次换 Token 需一次额外请求
存储依赖无(JWT 自包含)Refresh Token 需数据库或 Redis
主动吊销不能撤销单条 JWT,但窗口短(7d)可以删存储中的 Refresh Token 立即吊销
实现复杂度Middleware 几行新增端点、存储层、客户端换 Token 逻辑
适用场景中小型站点大规模、需要即时吊销的场景

如果需要即时吊销能力(比如用户改密码后踢出所有 Session),滑动续期方案也有低成本解法:JWT payload 里带一个 session_version 字段,Middleware 比对用户表中的版本号,不一致就清掉 Cookie。改密码时 bump 版本号,所有旧 Token 即刻失效。这比 Refresh Token 方案简单得多。

性能考量

续期不是每次请求都做。绝大多数请求的流程是:

读 Cookie → 验证签名 → 检查剩余时间 → 不需要续期 → 继续

只有剩余时间不足阈值的那次请求才会触发一次 JWT 签名操作。以 7 天窗口为例,一个每天访问的用户大约每 3-4 天触发一次续期。

JWT 验证是纯计算(HMAC 校验),无 IO;签名同理。开销可以忽略。

安全注意事项

  1. HttpOnly Cookie——JavaScript 无法读取,XSS 无法窃取 Token。localStorage 存 Token 反而是更危险的选择
  2. Secure 标记——生产环境 Cookie 必须设 secure: true,防止 HTTP 传输时泄露
  3. 密钥管理——JWT 签名密钥必须从环境变量注入,硬编码回退值只用于开发环境。生产漏配等于公开密钥
  4. 窗口长度选择——7 天是常见选择。太长(30d)风险周期大,太短(1d)续期频率高且用户体验差。3-14 天是比较合理的范围
  5. 签名算法——HS256(对称)足够应对中小站点场景;如果需要多方验签,换 RS256(非对称)

什么时候不该用滑动续期

  • 需要即时吊销单条 Token——滑动续期签发后无法主动撤销,只有窗口到期才自然失效。如果业务要求"点一下按钮立刻踢人",需要 Refresh Token 方案或版本号校验
  • 超大规模——每条请求过 Middleware 验证 JWT,虽然开销小,但在极高 QPS 下仍有累积成本。此时应考虑网关层集中处理,或缩短 Token 有效期减少验证次数
  • 纯 SPA + API 架构——如果前端完全不做 SSR,所有数据通过 API 获取,页面导航不经过服务端,Middleware 拦截不到页面请求。此时需要客户端主动续期(换 Token 端点),本质上就是双 Token 方案了

小结

滑动续期是一种"够用就好"的 Session 管理策略:

  • 对用户透明——活跃用户永远不会被踢出,不活跃用户自然过期
  • 对服务端轻量——Middleware 几行代码,不需要额外存储
  • 安全性可控——7 天窗口 + HttpOnly Cookie + Secure 标记,足够应对大多数场景

它不是万能方案——需要即时吊销或纯 SPA 架构时,双 Token 更合适。但对于 SSR 混合架构的中小站点,滑动续期是投入产出比最高的选择。

Thanks for reading.

评论

登录后即可评论

去登录
加载中...