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有效期详解 展开,系统讲解timeout(长久有效期)与active-timeout(最低活跃频率)两种 Token 自动过期策略的区别、配置方法与续签机制,并结合 sa-token-core 模块的源码实现说明“冻结检查”“自动续签”“手动续签”的底层调用链。读完本文,你可以正确配置两类过期策略,理解 Token 被冻结与被删除的本质差异,并能通过checkActiveTimeout()、updateLastActiveToNow()、renewTimeout()等 API 按业务需要手动控制续签。
一、两种过期策略概览与配置
Sa-Token 提供两种 Token 自动过期策略:
- timeout:Token 的长久有效期。到达该时长后 Token 必然过期,缓存数据被删除,必须重新登录才能继续访问。
- active-timeout:Token 的最低活跃频率。超过该时长未访问系统,Token 会被冻结(而非删除),重新登录后恢复,且活跃期内持续访问可不断续签。
两者均可通过 yaml 或 properties 配置文件设置,配置方法如下:
yaml 风格:
sa-token: # token 有效期(单位:秒),默认30天,-1代表永不过期 timeout: 2592000 # token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结 active-timeout: -1properties 风格:
# token 有效期(单位:秒),默认30天,-1代表永不过期 sa-token.timeout=2592000 # token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结 sa-token.active-timeout=-1默认值与源码定义
上述默认值可以直接在配置模型类 SaTokenConfig.java 中得到印证:
/** token 有效期(单位:秒) 默认30天,-1 代表永久有效 */ private long timeout = 60 * 60 * 24 * 30; // 即 2592000 秒 /** * token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结, * 默认-1 代表不限制,永不冻结(例如可以设置为 1800 代表 30 分钟内无操作就冻结) */ private long activeTimeout = -1;即:timeout 默认 30 天,active-timeout 默认 -1(永不冻结)。此外,该配置类中还提供了两个与有效期密切相关的扩展参数(源码见 SaTokenConfig.java 与 SaTokenConfig.java):
| 参数 | 默认值 | 作用 |
|---|---|---|
timeout | 2592000(30天) | Token 长久有效期,-1 代表永久有效 |
active-timeout | -1 | 最低活跃频率,-1 代表永不冻结 |
dynamic-active-timeout | false | 是否启用动态 active-timeout(按 token 粒度存储各自的活跃时长,如不需要请设置为 false,节省缓存请求次数) |
auto-renew | true | 是否打开自动续签 active-timeout(为 true 时,框架在每次直接或间接调用 getLoginId() 时进行一次过期检查与续签操作) |
二、timeout 与 active-timeout 的核心区别:银行卡类比
两者的区别可以通过官方文档中的例子来理解:
- 假设你到银行要存钱,首先就要办理一张卡(要访问系统接口先登录)。
- 银行为你颁发一张储蓄卡(系统为你颁发一个 Token),以后每次存取钱都要带上这张卡(后续每次访问系统都要提交 Token)。
- 银行为这张卡设定两个过期时间:
- 第一个是
timeout,代表这张卡的长久有效期,就是指这张卡最长能用多久。假设timeout=3年,那么 3 年后此卡将被银行删除,想要继续来银行办理业务必须重新办卡(Token 过期后想要访问系统必须重新登录)。 - 第二个就是
active-timeout,代表这张卡的最低活跃频率限制,就是指这张卡必须每隔多久来银行一次。假设active-timeout=1月,你如果超过 1 月不来办一次业务,银行就将你的卡冻结,列为长期不动户(Token 长期不访问系统,被冻结,但不会被删除)。
- 第一个是
- 两个过期策略可以单独配置,也可以同时配置,只要有其中一个有效期超出了范围,这张卡就会变得不可用(两个有效期只要有一个过期了,Token 就无法成功访问系统了)。
一句话概括:timeout 管“删不删”,active-timeout 管“冻不冻”。timeout 过期后缓存数据被清除,必须重新登录;active-timeout 过期后 Token 只是进入“冻结”状态,数据仍在,用户重新登录(或按框架设计解冻)后可恢复使用。
三、timeout:长久有效期
对timeout策略的详细解释:
timeout代表 Token 的长久有效期,单位/秒,例如将其配置为 2592000(30天),代表在 30 天后,Token 必定过期,无法继续使用。timeout无法续签,想要继续使用必须重新登录。v1.29.0+ 版本新增续期方法:StpUtil.renewTimeout(100)。timeout的值配置为 -1 后,代表永久有效,不会过期。
源码实现:renewTimeout 如何续期
从源码结构看,renewTimeout的完整实现位于 StpLogic.java,其执行链路为:
- 续期缓存数据:更新 Token → loginId 映射缓存的剩余有效期;
- 续期客户端 Cookie 有效期:当开启 Cookie 读取时,同步刷新 Cookie 的 Max-Age(若
timeout = -1代表永久,但一般浏览器不支持永久 Cookie,源码将其设置为Integer.MAX_VALUE避免数据溢出); - 续期 Token-Session:若当前 Token 存在 Token-Session,则调用
tokenSession.updateTimeout(timeout); - 续期 Account-Session:调用
session.updateMinTimeout(timeout),保证账号会话的存活时间不小于新 timeout; - 更新最后活跃时间:若开启了活跃检查,同步续期
last-active缓存 key 的存活时间; - 发布事件:
SaTokenEventCenter.doRenewTimeout(loginType, loginId, tokenValue, timeout),允许监听器感知续期动作。
同时源码也做了边界保护:若 Token 指向的 loginId 为空、或 token 不合法(Session 中无对应终端),会提前返回或抛出SaTokenException,避免写入意外数据。
timeout 过期的异常场景
当 Token 达到 timeout 后,缓存中的 Token→loginId 映射会被替换为“过期标记”,getLoginId()检查到该标记后抛出NotLoginException。其场景常量定义在 NotLoginException.java:
/** 表示 token 已过期 */ public static final String TOKEN_TIMEOUT = "-3"; public static final String TOKEN_TIMEOUT_MESSAGE = "token 已过期";该判断位于getLoginId()认证流程的第 4 步,完整流程见 StpLogic.java:
// 4、如果这个 token 指向的是值是:过期标记,则抛出:token 已过期 if(loginId.equals(NotLoginException.TOKEN_TIMEOUT)) { throw NotLoginException.newInstance(loginType, TOKEN_TIMEOUT, TOKEN_TIMEOUT_MESSAGE, tokenValue).setCode(SaErrorCode.CODE_11013); } // 7、token 活跃频率检查 checkActiveTimeoutByConfig(tokenValue);可以看到:timeout 过期(第 4 步)先于 active-timeout 冻结检查(第 7 步),两者彼此独立、互不干扰,这正是“两个策略可以同时使用”的源码依据。
四、active-timeout:最低活跃频率
对active-timeout策略的详细解释:
active-timeout代表最低活跃频率,单位/秒,例如将其配置为 1800(30分钟),代表用户如果 30 分钟无操作,则此 Token 会立即过期(被冻结,但不会删除掉)。- 如果在 30 分钟内用户有操作,则会再次续签 30 分钟,用户如果一直操作则会一直续签,直到连续 30 分钟无操作,Token 才会过期。
active-timeout的值配置为 -1 后,代表永久有效,不会过期,此时也无需频繁续签。
源码实现:最后活跃时间如何存储与判定
Sa-Token 在缓存中为每个 Token 维护一个“最后活跃时间”记录,其 key 拼接规则见 StpLogic.java:
public String splicingKeyLastActiveTime(String tokenValue) { return getConfigOrGlobal().getTokenName() + ":" + loginType + ":last-active:" + tokenValue; }即格式为{tokenName}:{loginType}:last-active:{tokenValue},value 为 13 位毫秒时间戳;该缓存的存活时间与 Token 本身的 timeout 保持一致(见setLastActiveToNow方法,StpLogic.java)。
冻结判定逻辑封装在isFreeze()方法中(StpLogic.java),其核心计算为:
- 获取这个 token 的剩余活跃有效期
activeTimeout; - 若值为 -1(
SaTokenDao.NEVER_EXPIRE),代表此 token 被设置永不冻结,返回 false; - 若值为 -2(
SaTokenDao.NOT_VALUE_EXPIRE,代表缓存中查不到最后活跃时间),则判定为已冻结。
其中“剩余活跃有效期”的计算公式为允许的最大时间差 - 实际时间差:从缓存中取出最后活跃时间,计算与当前时刻的时间差(秒),若时间差超过active-timeout允许值,则返回 -2 表示已冻结。相关常量定义在 SaTokenDao.java:
/** 常量,表示一个 key 永不过期 */ long NEVER_EXPIRE = -1; /** 常量,表示系统中不存在这个缓存 */ long NOT_VALUE_EXPIRE = -2;当 Token 被冻结后,getLoginId()流程末尾的活跃检查会抛出NotLoginException,其类型为“token 已被冻结”:
/** 表示 token 已被冻结 */ public static final String TOKEN_FREEZE = "-6"; public static final String TOKEN_FREEZE_MESSAGE = "token 已被冻结";见 NotLoginException.java。前端可根据异常类型-6区分“已过期(-3,需重新登录)”与“已冻结(-6,活跃度不足)”两类场景,分别做相应提示。
自动续签机制:checkActiveTimeoutByConfig
如果active-timeout配置了大于零的值,Sa-Token 会在登录时开始计时,在每次直接或间接调用getLoginId()、getTokenSession()时进行一次冻结检查与续签操作。此时的两种情况是:
- 会话无操作时间太长,Token 已经被冻结,此时框架会抛出
NotLoginException异常(冻结场景,对应源码常量TOKEN_FREEZE); - 会话在
active-timeout有效期内通过检查,此时 Token 可以成功续签。
从源码看,该逻辑集中在 StpLogic.java 的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()在单次请求上下文中缓存检查结果,避免一个请求中多次调用getLoginId()(例如一次经过多个鉴权注解)时重复读写缓存,源码注释明确说明这是“避免一次请求多次校验,造成不必要的性能消耗”; - 策略可替换:是否自动续签由
SaStrategy.instance.autoRenew策略函数决定,其默认实现(SaStrategy.java)读取全局配置的autoRenew参数,开发者可通过SaStrategy.instance.autoRenew = ...覆盖为自定义判断逻辑(例如按账号、按接口差异化控制续签); - 续签目标明确:注释特别强调“此处的续签是在续 active-timeout,而非 timeout”,即自动续签只刷新最后活跃时间,不会延长 Token 的长久有效期。
续签动作本身由updateLastActiveToNow完成(StpLogic.java),本质是把last-active缓存的 value 更新为当前毫秒时间戳:
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 供开发者操作:
StpUtil.checkActiveTimeout():检查当前 Token 是否已经被冻结,如果是则抛出异常;StpUtil.updateLastActiveToNow():续签当前 Token(将 [最后操作时间] 更新为当前时间戳)。
注意:在手动续签时,即使 Token 已经被冻结也可续签成功(解冻)。如果此场景下需要提示续签失败,可采用先检查再续签的形式保证 Token 有效性,例如:
// 先检查是否已被冻结 StpUtil.checkActiveTimeout(); // 检查通过后继续续签 StpUtil.updateLastActiveToNow();该行为在源码 Javadoc 中同样有明确声明(StpLogic.java):“请注意: 即使 token 已被冻结 也可续签成功,如果此场景下需要提示续签失败,可在此之前调用 checkActiveTimeout() 强制检查是否冻结即可”。
关闭自动续签:auto-renew=false
同时,你还可以关闭框架的自动续签(在配置文件中配置auto-renew=false),此时续签操作完全由开发者控制,框架不再自动进行任何续签操作:
sa-token: timeout: 2592000 active-timeout: 1800 auto-renew: false # 关闭自动续签,由业务代码手动调用 updateLastActiveToNow()对应配置项定义见 SaTokenConfig.java:
/** * 是否打开自动续签 activeTimeout * (如果此值为 true, 框架会在每次直接或间接调用 getLoginId() 时进行一次过期检查与续签操作) */ private Boolean autoRenew = true;关闭自动续签的典型场景:只在特定接口(如“心跳”接口)里主动续签,其余接口只检查不续签,从而精确控制“活跃”的业务语义。
为其它 Token 续签
如果你需要给其它 Token(而非当前会话 Token)续签:
// 为指定 Token 续签 StpUtil.stpLogic.updateLastActiveToNow(tokenValue);对应实现为updateLastActiveToNow(String tokenValue)重载方法(StpLogic.java),它会定位到指定 Token 的last-active缓存 key 并刷新时间戳,常用于后台任务为“挂起的长连接会话”保活等场景。
六、timeout 与 active-timeout 可以同时使用吗
可以同时使用!两者的认证逻辑彼此独立,互不干扰,可以同时使用。
从源码结构看,这一结论的依据是getLoginId()中的两级独立检查(StpLogic.java):第 4 步通过TOKEN_TIMEOUT标记检查 timeout 是否过期,第 7 步通过checkActiveTimeoutByConfig检查 active-timeout 是否冻结,二者各查各的缓存 key(token 映射缓存 vslast-active缓存),任何一个不通过都会阻断本次请求——与银行卡例子中“只要有一个过期,卡就不可用”的语义完全一致。
一个常见的组合配置:timeout: 2592000(30 天长有效期)+active-timeout: 1800(30 分钟不活跃即冻结),既能在用户持续使用时保持会话“在线”,又能在长期闲置时快速失效,而不必像只配置 timeout 那样等待 30 天后自然过期。
七、哪些方法支持自动续签 active-timeout
凡是直接或间接调用过getLoginId()、getTokenSession()的StpUtil方法,都会触发活跃检查与自动续签。包括但不限于:
| 包括但不限于这些 |
|---|
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 |
从源码上验证这一结论:getTokenSession(boolean isCreate)在进入会话逻辑前同样调用了checkActiveTimeoutByConfig(tokenValue)(StpLogic.java),因此上表中基于 Session 的方法(getSession()、getTokenSession()及其衍生方法)确实与登录态校验走同一条活跃检查链路。
八、测试用例佐证
core 模块自带针对活跃频率机制的单元测试 StpLogicActiveTimeoutTest.java,其中若干用例可以直接作为上述行为的“可执行文档”:
- 续签后剩余活跃时长正确(L52-L65):配置
activeTimeout=180,登录并调用updateLastActiveToNow()后,getTokenActiveTimeout()返回值应落在 [175, 180] 区间内,且checkActiveTimeout()不抛异常; - active-timeout=-1 永不冻结(L80-L92):
getTokenActiveTimeoutByToken返回SaTokenDao.NEVER_EXPIRE; - 冻结判定(L94-L126):手工把
last-activekey 的时间戳回拨到 60 秒前(超过 activeTimeout=10 秒),或干脆删除该 key,getTokenActiveTimeoutByToken均返回NOT_VALUE_EXPIRE(即已冻结)。这里也说明了一个实现细节:最后活跃时间记录缺失时,同样按“冻结”处理,属于保守的安全策略; - renewTimeout 边界行为(L142-L191):开启读 Cookie 时
renewTimeout(7200)可正常更新 Token 超时;Session 中无对应终端时抛出SaTokenException;对无效 Token 调用renewTimeout则静默返回(no-op);续期后会同步更新 Token-Session 超时与last-active记录。
运行这些用例的模块为 sa-token-core,可直接执行该模块的测试来验证本文章所述的过期与续签行为。
九、总结:选型速查表
| 维度 | timeout | active-timeout |
|---|---|---|
| 语义 | 长久有效期(硬期限) | 最低活跃频率(软期限) |
| 过期后果 | Token 缓存数据被删除 | Token 被冻结,数据保留 |
| 恢复方式 | 必须重新登录 | 重新登录 / 手动updateLastActiveToNow()解冻 |
| 是否自动续签 | 否(需 v1.29.0+ 手动renewTimeout) | 是(auto-renew=true时随鉴权调用自动续签) |
| 默认值 | 2592000 秒(30天) | -1(永不冻结) |
| 配置为 -1 | 永久有效 | 永久有效,且无需频繁续签 |
| 异常场景值 | -3(token 已过期) | -6(token 已被冻结) |
| 核心源码 | StpLogic.javarenewTimeout | StpLogic.javacheckActiveTimeoutByConfig |
掌握上述区别后,典型实践是:对强安全场景(金融、管理后台)配置较短的active-timeout快速冻结闲置会话,配合合理的timeout兜底硬期限;对弱安全场景(内容站、工具类接口)可直接保持active-timeout: -1,仅依赖timeout控制生命周期。所有参数均可在 SaTokenConfig.java 中查阅完整注释,本文结论均基于当前仓库源码,适用于 Sa-Token v1.29.0+ 版本(renewTimeout为 v1.29.0 新增)。
【免费下载链接】Sa-Token✨ 开源、免费、一站式 Java 权限认证框架,让鉴权变得简单、优雅!—— 登录认证、权限认证、分布式 Session 会话、微服务网关鉴权、SSO 单点登录、OAuth2.0 统一认证、jwt 集成、API Key 秘钥授权、API 参数签名项目地址: https://gitcode.com/GitHub_Trending/sa/Sa-Token
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考