news 2026/9/14 18:01:21

Sa-Token Token 有效期详解:timeout 与 active-timeout 双过期策略及自动续签机制

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Sa-Token Token 有效期详解:timeout 与 active-timeout 双过期策略及自动续签机制

Sa-Token Token 有效期详解:timeout 与 active-timeout 双过期策略及自动续签机制

【免费下载链接】Sa-Token✨ 开源、免费、一站式 Java 权限认证框架,让鉴权变得简单、优雅!—— 登录认证、权限认证、分布式 Session 会话、微服务网关鉴权、SSO 单点登录、OAuth2.0 统一认证、jwt 集成、API Key 秘钥授权、API 参数签名项目地址: https://gitcode.com/GitHub_Trending/sa/Sa-Token

本文基于 Sa-Token 官方文档 Token有效期详解 展开,系统讲解 Sa-Token 提供的两种 Token 自动过期策略——timeout(长久有效期)与active-timeout(最低活跃频率)的配置方式、冻结与过期语义差异、手动续签 API 以及自动续签的底层实现链路。读完后你可以准确配置 Token 有效期、理解"冻结但删除"的两种状态区别,并能在业务中自定义续签时机。

两种过期策略总览

Sa-Token 提供两种 Token 自动过期策略,分别是timeoutactive-timeout,配置方法如下:

sa-token: # token 有效期(单位:秒),默认30天,-1代表永不过期 timeout: 2592000 # token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结 active-timeout: -1
# token 有效期(单位:秒),默认30天,-1代表永不过期 sa-token.timeout=2592000 # token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结 sa-token.active-timeout=-1

两者的区别,可以通过下面的例子体现:

  1. 假设你到银行要存钱,首先就要办理一张卡(要访问系统接口先登录)。
  2. 银行为你颁发一张储蓄卡(系统为你颁发一个 Token),以后每次存取钱都要带上这张卡(后续每次访问系统都要提交 Token)。
  3. 银行为这张卡设定两个过期时间:
    • 第一个是timeout,代表这张卡的长久有效期,就是指这张卡最长能用多久。假设timeout=3年,那么 3 年后此卡将被银行删除,想要继续来银行办理业务必须重新办卡(Token 过期后想要访问系统必须重新登录)。
    • 第二个就是active-timeout,代表这张卡的最低活跃频率限制,就是指这张卡必须每隔多久来银行一次。假设active-timeout=1月,如果你超过 1 月不来办一次业务,银行就将你的卡冻结,列为长期不动户(Token 长期不访问系统,被冻结,但不会被删除)。
  4. 两个过期策略可以单独配置,也可以同时配置,只要有其中一个有效期超出了范围,这张卡就会变得不可用(两个有效期只要有一个过期了,Token 就无法成功访问系统了)。

从源码结构看,两个配置项默认值定义在 SaTokenConfig 中:

/** token 有效期(单位:秒) 默认30天,-1 代表永久有效 */ private long timeout = 60 * 60 * 24 * 30;

timeout默认为 30 天(2592000 秒),与文档描述一致;active-timeout默认 -1 代表永不冻结。此外 SaTokenConfig 中保留了@Deprecatedactivity-timeout旧配置项,若误用会输出"配置项已过期,请更换:sa-token.activity-timeout -> sa-token.active-timeout"的告警,升级时需注意区分拼写。

timeout:长久有效期

  1. timeout代表 Token 的长久有效期,单位/秒,例如将其配置为 2592000(30 天),代表在 30 天后,Token 必定过期,无法继续使用。
  2. timeout无法续签,想要继续使用必须重新登录。v1.29.0+ 版本新增续期方法:StpUtil.renewTimeout(100)
  3. timeout的值配置为 -1 后,代表永久有效,不会过期。

源码印证:renewTimeout 到底续了什么

续期能力的实现位于 StpLogic#renewTimeout,其处理流程可以拆分为七步:

  1. 校验 token 指向的 loginId 是否存在,为空则直接返回;
  2. 校验 token 合法性:查不到对应 Access-Session 会话或终端信息时抛出SaTokenException("未能查询到对应 Access-Session 会话,无法续期");
  3. 续期 token 本身的有效期(改 dao 中 key 的 ttl);
  4. 续期该 token 的 Token-Session 有效期;
  5. 续期该 token 指向账号的 Account-Session 有效期(session.updateMinTimeout(timeout));
  6. 若开启了 active-timeout 检查,同步更新"最后活跃时间"记录的有效期;
  7. 发布SaTokenEventCenter.doRenewTimeout续期事件,供全局监听器感知。

值得注意的细节在 StpLogic#renewTimeout(long) 中:续期缓存数据之外,如果开启读 Cookie(isReadCookie()),还会同步续期客户端 Cookie 的有效期;当timeout = -1(永久)或超过Integer.MAX_VALUE时,由于浏览器一般不支持永久 Cookie,代码会将其收敛为Integer.MAX_VALUE,避免数据溢出。

active-timeout:最低活跃频率

  1. active-timeout代表最低活跃频率,单位/秒,例如将其配置为 1800(30 分钟),代表用户如果 30 分钟无操作,则此 Token 会立即过期(被冻结,但不会删除掉)。
  2. 如果在 30 分钟内用户有操作,则会再次续签 30 分钟,用户如果一直操作则会一直续签,直到连续 30 分钟无操作,Token 才会过期。
  3. active-timeout的值配置为 -1 后,代表永久有效,不会过期,此时也无需频繁续签。

源码印证:冻结是如何判定的

"冻结"的判定核心是 StpLogic#getTokenActiveTimeoutByToken 中的计算公式:

// 实际时间差 long timeDiff = (System.currentTimeMillis() - lastActiveTime) / 1000; // 该 token 允许的时间差 long allowTimeDiff = getTokenUseActiveTimeoutOrGlobalConfig(tokenValue); if(allowTimeDiff == SaTokenDao.NEVER_EXPIRE) { // 如果允许的时间差为 -1 ,则代表永不冻结,此处需要立即返回 -1 return SaTokenDao.NEVER_EXPIRE; } // 校验这个时间差是否超过了允许的值 // 计算公式为: 允许的最大时间差 - 实际时间差,判断是否 < 0, 如果是则代表已经被冻结 ,返回-2 long activeTimeout = allowTimeDiff - timeDiff; if(activeTimeout < 0) { return SaTokenDao.NOT_VALUE_EXPIRE; // -2,代表已被冻结 } else { return activeTimeout; // 否则返回剩余活跃有效时间 }

返回值的语义约定为:-1NEVER_EXPIRE)代表永不冻结,-2NOT_VALUE_EXPIRE)代表已被冻结,其余为剩余活跃秒数。StpLogic#isFreeze 正是基于这套返回值判断 token 是否处于冻结状态。

这与"冻结不删除"的语义完全对应:被冻结的 Token 在缓存中依然存在(最后活跃时间记录还在),只是检查时会被拒绝;而timeout过期后 key 的 ttl 到期由缓存自然删除。

当冻结态 Token 发起请求时,StpLogic#checkActiveTimeout 会抛出NotLoginException

public void checkActiveTimeout(String tokenValue) { if (isFreeze(tokenValue)) { throw NotLoginException.newInstance(loginType, TOKEN_FREEZE, TOKEN_FREEZE_MESSAGE, tokenValue) .setCode(SaErrorCode.CODE_11016); } }

其中场景值TOKEN_FREEZE与异常码定义在 NotLoginException:

/** 表示 token 已被冻结 */ public static final String TOKEN_FREEZE = "-6"; public static final String TOKEN_FREEZE_MESSAGE = "token 已被冻结";

也就是说,前端捕获到"未登录"异常时,可以通过场景值-611016错误码区分出"token 已被冻结"这一特定情形,与 token 无效(-2)、超时(-4)、被顶下线(-5)等异常场景互不混淆。

单元测试印证

仓库内置的 StpLogicActiveTimeoutTest 覆盖了上述关键行为,例如:

  • 登录 10001 后调用updateLastActiveToNow()getTokenActiveTimeout()返回值应落在175 ~ 180秒区间(配置active-timeout=180),且checkActiveTimeout()不抛异常;
  • active-timeout配置为 -1 时,getTokenActiveTimeoutByToken()应返回SaTokenDao.NEVER_EXPIRE(永不冻结);
  • 配置active-timeout=10秒并等待超时后,对应方法返回SaTokenDao.NOT_VALUE_EXPIRE(冻结态)。

这些用例与本文"计算公式 + 返回值约定"的源码分析一一对应。

关于 active-timeout 的续签

如果active-timeout配置了大于零的值,Sa-Token 会在登录时开始计时,在每次直接或间接调用getLoginId()getTokenSession()时进行一次冻结检查与续签操作。此时会有两种情况:

  1. 一种是会话无操作时间太长,Token 已经被冻结,此时框架会抛出NotLoginException异常(场景值 =-6,异常信息"token 已被冻结");
  2. 另一种则是会话在active-timeout有效期内通过检查,此时 Token 可以成功续签。

自动续签的实现入口是 StpLogic#checkActiveTimeoutByConfig:

public void checkActiveTimeoutByConfig(String tokenValue) { if(isOpenCheckActiveTimeout()) { // storage.get(key, () -> {}) 可以避免一次请求多次校验,造成不必要的性能消耗 SaHolder.getStorage().get(SaTokenConsts.TOKEN_ACTIVE_TIMEOUT_CHECKED_KEY, () -> { // 1、检查此 token 的最后活跃时间是否已经超过了 active-timeout 的限制, // 如果是则代表其已被冻结,需要抛出:token 已被冻结 checkActiveTimeout(tokenValue); // 2、如果配置了自动续签功能, 则: 更新这个 token 的最后活跃时间 // (注意此处的续签是在续 active-timeout,而非 timeout) if(SaStrategy.instance.autoRenew.apply(this)) { updateLastActiveToNow(tokenValue); } return true; }); } }

这段代码体现了两个工程细节:

  • 单次请求内去重:借助SaHolder.getStorage().get(key, supplier),同一请求中多次调用getLoginId()等触发续签的方法时,冻结检查与续签只执行一次,避免不必要的缓存读写;
  • 续签策略可插拔:是否续签由 SaStrategy#autoRenew 这个策略函数决定,默认为 true,可通过配置项autoRenew=false或自定义策略覆盖。

而续签动作本身 StpLogic#updateLastActiveToNow 的实现非常轻量——把"最后活跃时间"key 的 value 更新为当前时间戳 + 本次允许的 active-timeout,并刷新该 key 的过期时间:

public void updateLastActiveToNow(String tokenValue) { String key = splicingKeyLastActiveTime(tokenValue); String value = new SaValue2Box(System.currentTimeMillis(), getTokenUseActiveTimeout(tokenValue)).toString(); getSaTokenDao().update(key, value); }

这正是"滑动窗口"语义的底层实现:每次有效访问都从当前时刻重新起算活跃窗口,只要用户持续活跃,窗口就不断顺延。

手动续签 active-timeout

可以!如果框架的自动续签算法无法满足您的业务需求,你可以进行手动续签,Sa-Token 提供两个 API 供你操作:

  1. StpUtil.checkActiveTimeout():检查当前 Token 是否已经被冻结,如果是则抛出异常
  2. StpUtil.updateLastActiveToNow():续签当前 Token(将 [最后操作时间] 更新为当前时间戳)

注意:在手动续签时,即使 Token 已经被冻结也可续签成功(解冻),如果此场景下需要提示续签失败,可采用先检查再续签的形式保证 Token 有效性。

例如以下代码:

// 先检查是否已被冻结 StpUtil.checkActiveTimeout(); // 检查通过后继续续签 StpUtil.updateLastActiveToNow();

这一注意事项与 StpLogic#updateLastActiveToNow 的 Javadoc 完全一致:"请注意: 即使 token 已被冻结 也可续签成功,如果此场景下需要提示续签失败,可在此之前调用 checkActiveTimeout() 强制检查是否冻结即可"——因为续签只是覆写"最后活跃时间"记录,并不会校验当前是否冻结。

同时,你还可以关闭框架的自动续签(在配置文件中配置autoRenew=false),此时续签操作完全由开发者控制,框架不再自动进行任何续签操作。对应源码中 SaTokenConfig#autoRenew 默认值为true

private Boolean autoRenew = true; // 是否打开自动续签 activeTimeout // (如果此值为 true, 框架会在每次直接或间接调用 getLoginId() 时进行一次过期检查与续签操作)

如果你需要给其它 Token 续签:

// 为指定 Token 续签 StpUtil.stpLogic.updateLastActiveToNow(tokenValue);

timeout 与 active-timeout 可以同时使用吗?

可以同时使用!两者的认证逻辑彼此独立,互不干扰,可以同时使用。

从源码结构看,timeout走的是 token key 自身的缓存 ttl 到期机制,而active-timeout依赖独立的"最后活跃时间"key 做滑动检查,两者互不感知;同时生效时,任何一个到期(过期或冻结)都会导致该 Token 无法通过认证。

StpUtil 类中哪些方法支持自动续签 active-timeout?

直接或间接调用过getLoginId()getTokenSession()的方法,包括但不限于:

包括但不限于这些
StpUtil.checkLogin()
StpUtil.getLoginId()
StpUtil.getLoginIdAsInt()
StpUtil.getLoginIdAsString()
StpUtil.getLoginIdAsLong()
---
StpUtil.getSession()
StpUtil.getTokenSession()
---
StpUtil.getRoleList()
StpUtil.hasRole()
StpUtil.hasRoleAnd()
StpUtil.hasRoleOr()
StpUtil.checkRole()
StpUtil.checkRoleAnd()
StpUtil.checkRoleOr()
---
StpUtil.getPermissionList()
StpUtil.hasPermission()
StpUtil.hasPermissionAnd()
StpUtil.hasPermissionOr()
StpUtil.checkPermission()
StpUtil.checkPermissionAnd()
StpUtil.checkPermissionOr()
---
StpUtil.openSafe()
StpUtil.isSafe()
StpUtil.checkSafe()
StpUtil.getSafeTime()
StpUtil.closeSafe()

以下注解都间接调用过getLoginId()方法:

支持自动续签的注解
@SaCheckLogin
@SaCheckRole
@SaCheckPermission
@SaCheckSafe

实践建议小结

结合本文的机制说明,给出几条可直接落地的配置建议:

场景推荐配置说明
管理后台/敏感业务timeout: 86400+active-timeout: 1800最长 1 天,30 分钟无操作即冻结,兼顾安全与体验
普通 Web 应用timeout: 2592000(默认)+active-timeout: -1(默认)30 天长久有效,不限制活跃
移动端长连接timeout: -1+active-timeout: 604800永不自然过期,但 7 天不活跃即冻结
需要业务自定义续签时机autoRenew: false+ 手动调用updateLastActiveToNow()框架不再自动续签,完全由业务代码控制

关键语义再强调一次:timeout到期是删除(必须重新登录),active-timeout到期是冻结(记录仍在,检查即被拒,场景值-6);renewTimeout()只能续timeoutupdateLastActiveToNow()只能续active-timeout,两者不可互相替代。

参考资料(仓库内路径)

  • 文档原文:sa-token-doc/fun/token-timeout.md
  • 配置项定义:SaTokenConfig
  • 冻结判定与续签核心实现:StpLogic
  • 冻结异常定义:NotLoginException
  • 续签策略函数:SaStrategy
  • 行为验证用例:StpLogicActiveTimeoutTest

【免费下载链接】Sa-Token✨ 开源、免费、一站式 Java 权限认证框架,让鉴权变得简单、优雅!—— 登录认证、权限认证、分布式 Session 会话、微服务网关鉴权、SSO 单点登录、OAuth2.0 统一认证、jwt 集成、API Key 秘钥授权、API 参数签名项目地址: https://gitcode.com/GitHub_Trending/sa/Sa-Token

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/14 18:00:12

Flutter+OpenHarmony实现MV播放功能的技术实践

1. MV播放功能整体设计思路 在音乐播放器App中实现MV播放功能&#xff0c;需要从技术架构和用户体验两个维度进行整体规划。与单纯的音频播放相比&#xff0c;MV播放涉及更复杂的媒体处理和UI交互。 1.1 技术架构选型 在Flutter for OpenHarmony环境下&#xff0c;MV播放的核…

作者头像 李华
网站建设 2026/9/14 17:59:32

C++与Matlab图像处理及人脸识别技术对比

1. 项目概述&#xff1a;跨平台图像处理与识别技术实践这个项目本质上是一次跨越编程语言边界的图像处理技术探索&#xff0c;核心在于比较C和Matlab两种技术栈在图像处理与人脸识别领域的实现差异。作为一名在计算机视觉领域工作多年的工程师&#xff0c;我经常需要面对这样的…

作者头像 李华
网站建设 2026/9/14 17:57:28

YOLO26涨点改进 | 独家创新、Neck特征融合改进篇 | ICLR 2025 | AFLB自适应频率学习融合模块全新优化、多频段特征自适应筛选与动态权重校准、攻克固定频段融合特征浪费与有效信息缺

目录 一、研究背景与YOLO26原生Neck融合固有缺陷 二、ICLR 2025 AFLB自适应频率学习融合模块核心创新原理 2.1 AFLB四大核心创新单元(ICLR2025标准架构) 2.2 AFLB适配YOLO26 Neck的独家定制优化 2.3 AFLB核心涨点优势汇总 三、多场景落地应用案例(多模态融合+通用检测…

作者头像 李华
网站建设 2026/9/14 17:53:36

消息队列顺序性与幂等设计:分布式系统下的高可靠实践

说实话我第一次正经做顺序消息的时候&#xff0c;是被线上事故推着往前走的。某天凌晨订单状态突然从已支付回退到已创建&#xff0c;用户支付成功的回调被后一条超时关闭消息先消费了&#xff0c;整个订单中心乱成一锅粥。后来排查半天&#xff0c;根子就出在消息顺序上&#…

作者头像 李华
网站建设 2026/9/14 17:53:31

基于muduo的高性能HTTP服务器设计与实现

1. 项目背景与核心目标最近在GitHub上看到一个基于muduo网络库实现的高性能HTTP服务器项目&#xff0c;让我想起了当年第一次接触网络编程时的场景。muduo作为国内知名的C网络库&#xff0c;其设计思想确实值得深入学习。这个仿muduo的HTTP服务器项目&#xff0c;核心目标是通过…

作者头像 李华