Sa-Token「记住我」模式全解析:临时 Cookie、持久 Cookie 与登录参数定制实战
【免费下载链接】Sa-Token✨ 开源、免费、一站式 Java 权限认证框架,让鉴权变得简单、优雅!—— 登录认证、权限认证、分布式 Session 会话、微服务网关鉴权、SSO 单点登录、OAuth2.0 统一认证、jwt 集成、API Key 秘钥授权、API 参数签名项目地址: https://gitcode.com/GitHub_Trending/sa/Sa-Token
几乎所有网站登录界面上都有一个「记住我」复选框:勾选后,即使关闭浏览器再重新打开,依旧保持登录状态,无需重复输入密码。本篇文章聚焦 Sa-Token 框架中「记住我」模式的完整实现:从默认行为、两种 Cookie 生命周期的底层原理,到前后端分离环境(APP、小程序、PC 浏览器)下的替代方案,再到利用
SaLoginParameter精细控制 Token 有效期与登录细节。读完本文,你将掌握用一条login()调用精确掌控会话寿命的完整实战能力。
一、Sa-Token 中的「记住我」默认就是开启的
在 Sa-Token 中,「记住我」并不是一个需要额外开启的功能,恰恰相反——Sa-Token 的登录授权默认就是「记住我」模式。
也就是说,日常最常见的这种写法:
StpUtil.login(10001);等价于「勾选了记住我」的登录:登录成功后框架会在浏览器写入一个持久 Cookie,用户关闭浏览器再打开,会话依然有效。
这一点在源码中有明确依据:SaTokenConfig.java 中isLastingCookie字段的默认值为true,同时默认timeout(Token 有效期)为60 * 60 * 24 * 30,即 30 天(同文件第 47 行)。
而要实现「非记住我」模式,只需在登录时显式传入第二个参数:
// 设置登录账号id为10001,第二个参数指定是否为[记住我] // 当此值为 false 后,关闭浏览器后再次打开需要重新登录 StpUtil.login(10001, false);二、实现原理:临时 Cookie 与持久 Cookie
Sa-Token 之所以能用一行代码切换「记住我」与「非记住我」,靠的是 Cookie 作为浏览器会话跟踪机制的两种生命周期形式:
- 临时 Cookie:有效期为本次会话,只要关闭浏览器窗口,Cookie 就会消失。
- 持久 Cookie:有效期为一个具体的时间段,在时间未到期之前,即使用户关闭浏览器,Cookie 也不会消失。
利用这一特性,「记住我」的实现逻辑非常直观:
| 用户操作 | 框架调用 | 写入浏览器的 Cookie | 重启浏览器后的效果 |
|---|---|---|---|
| 勾选「记住我」 | StpUtil.login(10001, true) | 持久 Cookie 储存 Token | Token 依然有效,保持登录 |
| 不勾选「记住我」 | StpUtil.login(10001, false) | 临时 Cookie 储存 Token | Token 随之消失,会话失效 |
源码级佐证:Cookie 的存活时长是如何计算的
从源码看,登录后的 Token 注入流程位于 StpLogic.java 的setTokenValue(tokenValue, loginParameter)方法:框架会将 Token 依次写入 Storage 存储器、浏览器 Cookie,并在需要时写入响应头。
其中写入 Cookie 时使用的存活时长,由 SaLoginParameter.java 中的getCookieTimeout()方法计算:
public int getCookieTimeout() { // 非持久 Cookie:返回 -1,代表内存级 Cookie,浏览器关闭即消失 if (!Boolean.TRUE.equals(getIsLastingCookie())) { return -1; } long _timeout = getTimeout(); // 持久 Cookie:直接使用 Token 的有效期作为 Cookie 存活时长 if(_timeout == SaTokenDao.NEVER_EXPIRE || _timeout > Integer.MAX_VALUE) { return Integer.MAX_VALUE; } return (int)_timeout; }可以看到核心规律:
isLastingCookie = false时,Cookie 的Max-Age被设为-1,即会话级(临时)Cookie,浏览器关闭即清除;isLastingCookie = true时,Cookie 的存活时长与 Token 有效期保持一致;- 若 Token 设为永久有效(
-1)或超出int上限,Cookie 存活时长取Integer.MAX_VALUE。
最终这个值通过new SaCookie().setMaxAge(cookieTimeout)写入 Cookie(见 StpLogic.java)。
值得注意的是,Token 本体在服务端存储的有效期与 Cookie 存活时长是两套独立的机制:持久 Cookie 只是让浏览器“留着这张令牌”,令牌在服务端是否过期,仍由timeout决定。因此更安全的实践是让 Cookie 存活时长 ≤ Token 服务端有效期,Sa-Token 默认的策略正是二者保持一致。
三、登录时的四种重载写法
StpLogic提供了多种登录重载,方便在不同场景下快速指定登录细节(对应静态方法见 StpUtil.java):
// 1. 默认登录:使用全局配置(默认即“记住我”,有效期 30 天) StpUtil.login(10001); // 2. 指定设备类型(用于同端互斥登录等场景) StpUtil.login(10001, "PC"); // 3. 指定是否为“记住我” StpUtil.login(10001, true); // 持久 Cookie StpUtil.login(10001, false); // 临时 Cookie,关浏览器即失效 // 4. 指定 Token 有效期(单位:秒),7 天有效 StpUtil.login(10001, 60 * 60 * 24 * 7);从 StpLogic.java 的实现可以看到,这些重载最终都会汇聚到login(id, SaLoginParameter):先把id与参数对象传入createLoginSession()创建会话,再把生成的 Token 注入当前客户端(Storage + Cookie + 可选响应头),也就是说登录行为的所有细节都由SaLoginParameter这一个参数模型统一承载。
四、登录时指定 Token 有效期
除了指定是否「记住我」,你还可以在登录时指定一个精确的 Token 有效时长:
// 指定 token 有效期(单位:秒),如下所示 token 七天有效 StpUtil.login(10001, new SaLoginParameter().setTimeout(60 * 60 * 24 * 7));这里的SaLoginParameter会以全局配置为默认值(见 SaLoginParameter.java 的setDefaultValues方法),你只需覆盖关心的字段即可,未指定的项自动沿用 SaTokenConfig 中的全局配置。
SaLoginParameter 全参数速查
SaLoginParameter是登录参数的完整 Model,覆盖了登录时的各种细节逻辑,常用参数如下:
StpUtil.login(10001, new SaLoginParameter() .setDeviceType("PC") // 此次登录的客户端设备类型,用于[同端互斥登录]时指定此次登录的设备类型 .setIsLastingCookie(true) // 是否为持久Cookie(临时Cookie在浏览器关闭时会自动删除,持久Cookie在重新打开后依然存在) .setTimeout(60 * 60 * 24 * 7) // 指定此次登录token的有效期, 单位:秒(如未指定,自动取全局配置的 timeout 值) .setActiveTimeout(60 * 60) // 指定此次登录token的最低活跃频率, 单位:秒(如未指定,则使用全局配置的 activeTimeout 值) .setToken("xxxx-xxxx-xxxx-xxxx") // 预定此次登录生成的Token .setIsWriteHeader(false) // 是否在登录后将 Token 写入到响应头 .setIsConcurrent(true) // 是否允许同一账号多地同时登录(false 时新登录挤掉旧登录) .setIsShare(true) // 多人登录同一账号时是否共用一个 token .setMaxLoginCount(-1) // 同一账号最大登录数量,-1 代表不限 );各字段含义与取值说明(依据 SaLoginParameter.java 源码注释):
| 参数 | 类型 | 说明 |
|---|---|---|
deviceType | String | 客户端设备类型(默认DEFAULT_LOGIN_DEVICE_TYPE),用于同端互斥登录 |
deviceId | String | 客户端设备 id |
timeout | long | Token 有效期(秒),未指定取全局timeout(默认 30 天,-1永久有效) |
activeTimeout | Long | Token 最低活跃频率(秒),超过该时间未访问系统会被冻结,默认-1不限制 |
isConcurrent | Boolean | 是否允许同一账号多地同时登录,false时新登录挤掉旧登录 |
isShare | Boolean | 多人登录同一账号时是否共用同一个 token |
maxLoginCount | int | 同一账号最大登录数量,-1不限(仅在isConcurrent=true, isShare=false时生效) |
maxTryTimes | int | 创建 token 时的最高循环次数,用于保证唯一性(-1不循环尝试) |
isLastingCookie | Boolean | 是否为持久 Cookie,即「记住我」开关 |
isWriteHeader | Boolean | 是否在登录后将 Token 写入响应头 |
token | String | 预定本次登录生成的 Token 值 |
extraData | Map | 扩展信息(仅在 JWT 模式下生效),可用setExtra(key, value)写入 |
rightNowCreateTokenSession | Boolean | 登录时是否立即创建 Token-Session |
cookie | SaCookieConfig | 本次登录的 Cookie 配置对象(domain、path、secure、httpOnly、sameSite 等),可通过setupCookieConfig(...)定制 |
写入响应头的注意事项
当isWriteHeader为true时,框架会把 Token 写入当前请求的响应头。从 StpLogic.java 的setTokenValueToResponseHeader()可以看到,框架除了写入 Token 本身,还会同时写入Access-Control-Expose-Headers响应头——否则跨域场景下前端 JS 将无法读取到该自定义响应头。这一细节在前后端分离开发时需要留意。
五、前后端分离模式下如何实现「记住我」
Cookie 虽好,却无法在前后端分离环境下使用——APP、小程序等客户端默认没有实现 Cookie 功能,因此任何基于 Cookie 的认证方案在纯前后端分离架构下都会失效。
好在这些客户端一般都提供了本地存储方案,我们可以用「前端手动控制 Token 生命周期」的方式达到同样的效果,核心思路是用本地持久存储模拟「持久 Cookie」、用会话级内存存储模拟「临时 Cookie」。
跨端框架(uni-app 等)示例
// 使用本地存储保存token,达到 [持久Cookie] 的效果(关掉应用再打开依然有效) uni.setStorageSync("satoken", "xxxx-xxxx-xxxx-xxxx-xxx"); // 使用 globalData 保存token,达到 [临时Cookie] 的效果(应用退出即失效) getApp().globalData.satoken = "xxxx-xxxx-xxxx-xxxx-xxx";PC 浏览器前后端分离示例
// 使用 localStorage 保存token,达到 [持久Cookie] 的效果 localStorage.setItem("satoken", "xxxx-xxxx-xxxx-xxxx-xxx"); // 使用 sessionStorage 保存token,达到 [临时Cookie] 的效果(关闭浏览器标签即失效) sessionStorage.setItem("satoken", "xxxx-xxxx-xxxx-xxxx-xxx");在这种模式下,前端的职责是:登录接口返回 Token 后,根据用户是否勾选「记住我」选择对应的存储介质保存;之后每次请求(拦截器/axios 请求头)携带该 Token 供服务端校验;退出登录或 Token 过期时再将其清除。后端则通过 Sa-Token 的 Token 读取机制从请求头或参数中获取凭证完成鉴权。
六、完整可运行的 Demo:RememberMeController
为了便于直接上手验证,仓库在sa-token-demo-case模块中提供了一个完整的控制器示例:RememberMeController.java,其中包含三种典型登录场景:
@RestController @RequestMapping("/RememberMe/") public class RememberMeController { // 记住我登录 ---- http://localhost:8081/RememberMe/doLogin?name=zhang&pwd=123456 @RequestMapping("doLogin") public SaResult doLogin(String name, String pwd) { if("zhang".equals(name) && "123456".equals(pwd)) { StpUtil.login(10001, true); return SaResult.ok("登录成功"); } return SaResult.error("登录失败"); } // 不记住我登录 ---- http://localhost:8081/RememberMe/doLogin2?name=zhang&pwd=123456 @RequestMapping("doLogin2") public SaResult doLogin2(String name, String pwd) { if("zhang".equals(name) && "123456".equals(pwd)) { StpUtil.login(10001, false); return SaResult.ok("登录成功"); } return SaResult.error("登录失败"); } // 七天免登录 ---- http://localhost:8081/RememberMe/doLogin3?name=zhang&pwd=123456 @RequestMapping("doLogin3") public SaResult doLogin3(String name, String pwd) { if("zhang".equals(name) && "123456".equals(pwd)) { StpUtil.login(10001, 60 * 60 * 24 * 7); return SaResult.ok("登录成功"); } return SaResult.error("登录失败"); } }三个接口分别演示了「记住我登录」「不记住我登录」「指定 7 天有效期登录」,启动该模块后可直接通过浏览器访问对应 URL 验证效果,是理解本文全部内容的最佳实践入口。
七、小结与选型建议
「记住我」功能在 Sa-Token 中实现成本极低,关键在于理解它的两层设计:
- Cookie 层面:
isLastingCookie决定写入持久 Cookie 还是临时 Cookie,控制“浏览器关闭后 Token 是否还在”; - 服务端层面:
timeout决定 Token 在服务端存储的有效期,控制“令牌本身何时过期”。
建议按业务场景选择:
- 默认登录:直接
StpUtil.login(id),即持久 Cookie + 30 天有效期,适合对会话时长要求宽松的场景; - 敏感操作:
StpUtil.login(id, false),临时 Cookie,关闭浏览器即失效,适合涉及资金、隐私等敏感功能; - 精细化控制:通过
SaLoginParameter组合isLastingCookie、timeout、activeTimeout、isConcurrent等参数,按接口粒度定制每一次登录行为; - 前后端分离:放弃 Cookie 方案,前端根据「记住我」勾选状态选择 localStorage / sessionStorage 保存 Token,配合响应头或请求参数传递凭证。
相关参考:RememberMeController 示例、SaLoginParameter 参数模型、StpLogic 登录实现、SaTokenConfig 全局配置。
【免费下载链接】Sa-Token✨ 开源、免费、一站式 Java 权限认证框架,让鉴权变得简单、优雅!—— 登录认证、权限认证、分布式 Session 会话、微服务网关鉴权、SSO 单点登录、OAuth2.0 统一认证、jwt 集成、API Key 秘钥授权、API 参数签名项目地址: https://gitcode.com/GitHub_Trending/sa/Sa-Token
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考