news 2026/9/13 12:11:12

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有效期详解 展开,系统讲解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: -1

properties 风格:

# 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):

参数默认值作用
timeout2592000(30天)Token 长久有效期,-1 代表永久有效
active-timeout-1最低活跃频率,-1 代表永不冻结
dynamic-active-timeoutfalse是否启用动态 active-timeout(按 token 粒度存储各自的活跃时长,如不需要请设置为 false,节省缓存请求次数)
auto-renewtrue是否打开自动续签 active-timeout(为 true 时,框架在每次直接或间接调用 getLoginId() 时进行一次过期检查与续签操作)

二、timeout 与 active-timeout 的核心区别:银行卡类比

两者的区别可以通过官方文档中的例子来理解:

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

一句话概括:timeout 管“删不删”,active-timeout 管“冻不冻”。timeout 过期后缓存数据被清除,必须重新登录;active-timeout 过期后 Token 只是进入“冻结”状态,数据仍在,用户重新登录(或按框架设计解冻)后可恢复使用。

三、timeout:长久有效期

timeout策略的详细解释:

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

源码实现:renewTimeout 如何续期

从源码结构看,renewTimeout的完整实现位于 StpLogic.java,其执行链路为:

  1. 续期缓存数据:更新 Token → loginId 映射缓存的剩余有效期;
  2. 续期客户端 Cookie 有效期:当开启 Cookie 读取时,同步刷新 Cookie 的 Max-Age(若timeout = -1代表永久,但一般浏览器不支持永久 Cookie,源码将其设置为Integer.MAX_VALUE避免数据溢出);
  3. 续期 Token-Session:若当前 Token 存在 Token-Session,则调用tokenSession.updateTimeout(timeout)
  4. 续期 Account-Session:调用session.updateMinTimeout(timeout),保证账号会话的存活时间不小于新 timeout;
  5. 更新最后活跃时间:若开启了活跃检查,同步续期last-active缓存 key 的存活时间;
  6. 发布事件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策略的详细解释:

  1. active-timeout代表最低活跃频率,单位/秒,例如将其配置为 1800(30分钟),代表用户如果 30 分钟无操作,则此 Token 会立即过期(被冻结,但不会删除掉)。
  2. 如果在 30 分钟内用户有操作,则会再次续签 30 分钟,用户如果一直操作则会一直续签,直到连续 30 分钟无操作,Token 才会过期。
  3. 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),其核心计算为:

  1. 获取这个 token 的剩余活跃有效期activeTimeout
  2. 若值为 -1(SaTokenDao.NEVER_EXPIRE),代表此 token 被设置永不冻结,返回 false;
  3. 若值为 -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()时进行一次冻结检查与续签操作。此时的两种情况是:

  1. 会话无操作时间太长,Token 已经被冻结,此时框架会抛出NotLoginException异常(冻结场景,对应源码常量TOKEN_FREEZE);
  2. 会话在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 供开发者操作:

  1. StpUtil.checkActiveTimeout():检查当前 Token 是否已经被冻结,如果是则抛出异常;
  2. 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,可直接执行该模块的测试来验证本文章所述的过期与续签行为。

九、总结:选型速查表

维度timeoutactive-timeout
语义长久有效期(硬期限)最低活跃频率(软期限)
过期后果Token 缓存数据被删除Token 被冻结,数据保留
恢复方式必须重新登录重新登录 / 手动updateLastActiveToNow()解冻
是否自动续签否(需 v1.29.0+ 手动renewTimeout是(auto-renew=true时随鉴权调用自动续签)
默认值2592000 秒(30天)-1(永不冻结)
配置为 -1永久有效永久有效,且无需频繁续签
异常场景值-3(token 已过期)-6(token 已被冻结)
核心源码StpLogic.javarenewTimeoutStpLogic.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),仅供参考

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

继电器选型避坑指南:从参数理解到工况匹配

1. 为什么“会用继电器”不等于“接上就能通电”?你有没有遇到过这种情况:手头有个标着“DC 12V 10A”的电磁继电器,线圈一通电,触点咔哒一声响,你以为万事大吉——结果接上负载后,不到半小时,继…

作者头像 李华
网站建设 2026/9/13 12:09:31

手写Verilog实现FPGA DDS信号发生器(正弦/方波/三角波)

简介:本资源是一个基于FPGA实现的简易信号发生器完整工程,面向电子工程专业学生、FPGA初学者及数字电路实践者,解决基础波形生成与可编程逻辑设计入门问题。项目支持正弦波、方波、锯齿波等常见波形输出,并具备任意波编辑能力&…

作者头像 李华
网站建设 2026/9/13 12:05:38

C++ STL list容器详解:原理、用法与性能优化

1. C STL list容器概述在C标准模板库(STL)中,list是一个双向链表容器,它允许在常数时间内进行任意位置的插入和删除操作。与vector和deque等顺序容器不同,list不支持随机访问,但它在中间位置插入和删除元素时具有显著优势。list容…

作者头像 李华
网站建设 2026/9/13 12:04:02

Elasticsearch基础查询语法与实战指南

1. Elasticsearch基础查询语法入门指南作为一款分布式搜索和分析引擎,Elasticsearch(简称ES)的查询语法是每个开发者必须掌握的核心技能。我在实际项目中使用ES已有5年多时间,今天就来分享最实用的基础查询语法,帮你快…

作者头像 李华