
Sa-Token 和项目场景放在一起看,才发现这事一点都不“小”。它本质上不是页面功能,而是 登录态存在哪里、活多久、浏览器关掉之后还认不认。这篇文章,我就结合 Sa-Token 的实现,把这件事彻底拆开。如果你们项目里也在做 Web 登录、前后端分离、APP / 小程序鉴权,这篇会很有用。
说实话,这种需求在 Demo 里特别好做。产品说要一个“记住我”,前端传个布尔值,后端接一下,页面上看着就像完成了。
但到了真实项目里,问题马上就会冒出来:
我后面越来越觉得,很多团队不是不会写这个功能。而是从一开始,就把问题理解窄了。
“记住我”真正要回答的,不是要不要勾选,而是登录态的生命周期怎么设计。
Sa-Token 这套设计里,登录授权默认就是 [记住我] 模式。
也就是说,如果你想实现的是“浏览器关掉后,下次必须重新登录”,你反而要在登录时显式写出来:
// 设置登录账号id为10001,第二个参数指定是否为[记住我],当此值为false后,关闭浏览器后再次打开需要重新登录
StpUtil.login(10001, false);这个点我第一次看到时,确实愣了一下。因为很多人脑子里的默认认知是:
但在工程实现层面,Sa-Token 更像是把“登录后默认保持状态”当成基础能力。你如果要“非记住我”,就显式关闭它。
这其实也提醒我们一件事:业务语义和框架默认值,最好不要靠猜。
浏览器这边,记住我 的底层并不玄学。本质上就是 Cookie 生命周期的区别。
Cookie 作为浏览器默认的会话跟踪机制,生命周期通常就两种:
所以 Sa-Token 这件事的实现逻辑非常直接:
[记住我] 时,调用 StpUtil.login(10001, true),写入的是 持久Cookie。[记住我] 时,调用 StpUtil.login(10001, false),写入的是 临时Cookie。到这里为止,逻辑其实很顺。但真正容易出事的,是很多人会把下面两个问题混成一个:
这两个问题有关,但不是一回事。后面我们专门说。
我当时踩到的第一个坑,就在这里。浏览器里用 Cookie 做“记住我”很自然。可一旦切到 APP、小程序、uni-app 这类前后端分离场景,Cookie 这条路就走不通了。
原因也很现实:这些客户端默认并没有完整实现浏览器那套 Cookie 会话机制。
这时候,你就得自己接管 Token 的存储策略。以 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 一律塞进 localStorage。这样一来,页面上虽然还有“记住我”按钮,但业务语义其实已经失真了。
也就是说,用户以为自己没勾选,系统却还是把他当成了长期登录。
这类问题最烦的地方在于,它不一定报错。它只是让“按钮文案”和“真实行为”慢慢分家。
这个认知点,我觉得特别重要。很多团队第一次做这个功能时,会天然把“记住我”和“延长 Token 有效期”绑定在一起。但 Sa-Token 其实把这两个维度拆开了。
比如你完全可以单独指定 Token 有效期:
// 示例1:
// 指定token有效期(单位: 秒),如下所示token七天有效
StpUtil.login(10001, new SaLoginParameter().setTimeout(60 * 60 * 24 * 7));
// ----------------------- 示例2:所有参数
// `SaLoginParameter`为登录参数Model,其有诸多参数决定登录时的各种逻辑,例如:
StpUtil.login(10001, new SaLoginParameter()
.setDevice("PC") // 此次登录的客户端设备类型, 用于[同端互斥登录]时指定此次登录的设备类型
.setIsLastingCookie(true) // 是否为持久Cookie(临时Cookie在浏览器关闭时会自动删除,持久Cookie在重新打开后依然存在)
.setTimeout(60 * 60 * 24 * 7) // 指定此次登录token的有效期, 单位:秒 (如未指定,自动取全局配置的 timeout 值)
.setToken("xxxx-xxxx-xxxx-xxxx") // 预定此次登录的生成的Token
.setIsWriteHeader(false) // 是否在登录后将 Token 写入到响应头
);这里面至少有两个参数,语义完全不同:
setIsLastingCookie(true):控制的是 Cookie 是不是持久化,影响“浏览器关掉再打开后还在不在”。setTimeout(...):控制的是 Token 的有效时长,影响“服务端认不认这个 Token”。这两个维度一拆开,很多线上现象就能解释通了:
用户重开浏览器后,本地凭证还在,但服务端可能已经判它过期了。
服务端本来还认这个 Token,但浏览器一关,本地凭证已经丢了,用户照样得重新登录。
所以我后面跟产品或者测试对这个需求时,会尽量不用一句模糊的“支持记住我”。而是直接问清楚两件事:
你把这两件事问清楚,需求至少一半不会跑偏。
如果只是做个 Demo,写到这里其实已经够了。但如果是线上系统,尤其是后台、SaaS 控制台、运营平台,我更建议你多补几层工程兜底。
我自己的经验里,至少要补这 5 件事:
这里我有个偏主观但挺实战的判断:
因为后台系统的设备环境通常更复杂,账号交叉使用、代操作、共享电脑的概率也更高。这时候你给了一个“长期保持登录”的按钮,后面补安全洞和补说明文案的成本,常常比你想象得高。
以前我会把“记住我”看成登录页上的一个小功能。现在我更愿意把它看成 登录态生命周期策略 的一个入口。
它真正决定的是:
代码当然不复杂。难的是你别把一个看起来很小的按钮,做成一个语义模糊、行为失控、出了问题没人能说清的功能。
说实话,很多认证问题都不是代码难,而是概念一开始就定义错了。
“记住我”不是一个表单勾选项,它本质上是在定义:登录态存在哪里、活多久、浏览器重启后还认不认。
你们项目里的“记住我”,现在是怎么做的?
欢迎在评论区分享你们的经验和教训!
相关文章推荐:
如果这篇文章帮到了你,不妨点个分享给同样需要的朋友吧! 你的每一次支持,都是我持续创作的动力!💪