Spring Security 的「记住我」(Remember-Me)是怎么实现的?有哪几种方式?
简化版
**「记住我」(Remember-Me)是让用户「关闭浏览器、下次再来还保持登录」的功能——即使 Session 过期了,也能通过一个长期有效的凭证自动登录。**Spring Security 提供两种实现:① 简单哈希 Token(TokenBasedRememberMeServices)——登录时生成一个 Token(用「用户名 + 过期时间 + 密码 + 密钥」做哈希),存到浏览器 Cookie 里;下次请求带这个 Cookie,服务端重新算哈希比对,一致就自动登录。优点:无状态、简单(不用存数据库);缺点:Token 里含用户信息、且无法主动失效(除非改密码——因为哈希里含密码)。② 持久化 Token(PersistentTokenBasedRememberMeServices)——服务端数据库存一个「series(不变的序列号)+ token(每次用后更新)」,Cookie 里存 series+token;每次自动登录后 token 更新,能检测「Token 被盗用」(如果同一个 series 的旧 token 被再次使用,说明被盗,可让整个 series 失效)。更安全(能检测盗用、能主动失效),但要存数据库。核心:Remember-Me = 一个存在 Cookie 里的长期凭证,让用户跨会话保持登录;简单哈希方式无状态但不够安全,持久化 Token 方式更安全能防盗用。
详细版
两种 Remember-Me 实现对比:
| 维度 | 简单哈希 Token | 持久化 Token |
|---|---|---|
| 存储 | 无(Cookie 自包含哈希) | 数据库(series + token) |
| Token 内容 | 用户名+过期时间+密码哈希 | series(固定)+ token(更新) |
| 安全性 | 较低(含用户信息、难失效) | 高(能检测盗用、能失效) |
| 盗用检测 | 不能 | 能(旧 token 再用则失效整个 series) |
| 主动失效 | 只能靠改密码 | 能(删数据库记录) |
| 依赖 | 无(无状态) | 需数据库表 |
// 配置 Remember-Me(Spring Security)
http.rememberMe(remember -> remember
.key("uniqueAndSecretKey") // 哈希用的密钥(简单方式)
.tokenValiditySeconds(7 * 24 * 3600) // 记住 7 天
// 持久化方式:配置 PersistentTokenRepository
.tokenRepository(persistentTokenRepository())
);
// 持久化 Token 仓库(用 JDBC 存 series + token)
@Bean
public PersistentTokenRepository persistentTokenRepository(DataSource ds) {
JdbcTokenRepositoryImpl repo = new JdbcTokenRepositoryImpl();
repo.setDataSource(ds); // 需要 persistent_logins 表
return repo;
}
<!-- 登录表单勾选「记住我」 -->
<input type="checkbox" name="remember-me"> 记住我
⚠️ Remember-Me 是「安全性和便利性的权衡」——它牺牲了一部分安全性来换取「不用频繁登录」的便利,所以要理解它的风险。核心风险是:Remember-Me 的 Cookie 是一个「长期有效的登录凭证」,一旦被窃取(XSS、抓包),攻击者就能长期冒充用户登录(比 Session 危险,Session 短、Remember-Me 长)。所以:① 必须走 HTTPS(防抓包窃取 Cookie)、Cookie 设
HttpOnly(防 XSS 读取);② 简单哈希方式的 Token 无法主动失效(用户登出也没用,因为哈希自包含),安全性较低;③ 持久化 Token 方式更安全——它能「检测盗用」(同一 series 的旧 token 被重复使用 = 有人用了盗来的旧 Cookie,此时让整个 series 失效、强制重新登录),还能主动删数据库记录失效。所以对安全敏感的系统用持久化 Token,且敏感操作(改密码、支付)应该要求重新输密码(不能只凭 Remember-Me)。
完整版教学
一、问题:Session 过期就要重新登录
先理解 Remember-Me 要解决什么:
正常登录的生命周期:
登录 → 服务端创建 Session → 浏览器存 Session ID(Cookie)
→ 后续请求带 Session ID → 服务端认得你(已登录)
Session 的问题(对"保持登录"):
① Session 有过期时间(如 30 分钟不活动就过期)
② 关闭浏览器,会话 Cookie 通常就没了(下次要重新登录)
→ 用户体验:每次来都要重新登录,烦
想要的(Remember-Me 的目标):
"关了浏览器、过几天再来,还是登录状态"
→ 即使 Session 过期/没了,也能自动登录
怎么做:
给浏览器一个"长期有效的凭证"(Remember-Me Cookie)
下次来时,即使没有 Session,凭这个凭证也能自动登录
→ 跨会话保持登录
所以 Remember-Me = 一个长期凭证,让用户不用频繁登录
Remember-Me 要解决「Session 过期就要重新登录」——正常登录靠 Session(有过期时间、关浏览器就没了),用户每次来都要重新登录。想要的是「关了浏览器过几天再来还是登录状态」(即使 Session 过期也能自动登录)。做法:给浏览器一个长期有效的凭证(Remember-Me Cookie),下次来即使没 Session 也能凭它自动登录(跨会话保持登录)。理解「Remember-Me 解决 Session 过期要重登、给浏览器长期凭证(Cookie)、下次没 Session 也能自动登录、跨会话保持登录」,就理解了它要解决的问题。
二、简单哈希 Token 方式
第一种实现是「简单哈希 Token」——无状态、自包含:
TokenBasedRememberMeServices(简单哈希):
登录时勾选"记住我" → 生成一个 Token 存到 Cookie
Token = 哈希(用户名 + 过期时间 + 密码 + 密钥)
具体:base64(username:过期时间:md5哈希(username:过期时间:password:key))
下次自动登录:
浏览器带这个 Remember-Me Cookie
服务端解出 username、过期时间
→ 检查是否过期
→ 用同样的算法重新算哈希,和 Cookie 里的比对
→ 一致 → 自动登录(加载用户、设置认证)
特点:
① 无状态——服务端不存任何东西,Token 自包含
(比对时靠重新计算哈希,不用查存储)
② 简单——不用数据库表
缺点:
① Token 里含用户信息(用户名、过期时间)
② 无法主动失效——因为哈希自包含、服务端不存
用户登出也没用(Token 还有效)
唯一能让它失效的:改密码(因为哈希含 password)
③ 安全性较低——Cookie 被盗就能长期冒充,且难以补救
所以简单哈希方式:简单、无状态,但安全性和可控性差
第一种「简单哈希 Token」(无状态自包含)——登录勾选记住我生成 Token 存 Cookie,Token = 哈希(用户名+过期时间+密码+密钥);下次带 Cookie,服务端解出用户名/过期时间、重新算哈希比对、一致就自动登录。特点:无状态(服务端不存、靠重算哈希比对)、简单(不用数据库)。缺点:Token 含用户信息、无法主动失效(哈希自包含、服务端不存、登出也没用,唯一失效办法是改密码因哈希含 password)、安全性较低(Cookie 被盗能长期冒充难补救)。理解「简单哈希 Token:Token=哈希(用户名+过期时间+密码+密钥)存 Cookie、重算哈希比对、无状态简单;缺点无法主动失效(登出没用,只能改密码)+安全性低」,就掌握了简单哈希方式。
三、持久化 Token 方式
第二种实现是「持久化 Token」——更安全、能防盗用:
PersistentTokenBasedRememberMeServices(持久化):
服务端数据库存一张表(persistent_logins),记录:
username(用户名)
series(序列号,一次"记住我"生成、不变)
token(令牌,每次自动登录后更新)
last_used(最后使用时间)
Cookie 里存:series + token
自动登录流程:
1. 浏览器带 Cookie(series + token)
2. 服务端用 series 查数据库,拿到存储的 token
3. 比对 Cookie 的 token 和数据库的 token:
- 一致 → 自动登录,★并生成新 token 更新数据库和 Cookie
(每次用后 token 都变,series 不变)
- 不一致(但 series 存在)→ ★盗用检测触发!
盗用检测(持久化方式的核心优势):
正常:每次自动登录后 token 更新,旧 token 作废
异常:如果有人用"旧的 token"(同一 series)来登录
→ 说明这个 Cookie 被盗用了(真用户和盗用者各持一份,
一个用了导致 token 更新,另一个的 token 就成了旧的)
→ 检测到"series 存在但 token 不匹配" → 判定盗用
→ 删除这个 series 的记录,让整个 series 失效
→ 真用户和盗用者都要重新登录(保护)
优点:
① 能检测盗用(旧 token 再用 → 失效)
② 能主动失效(删数据库记录)
③ 更安全(token 每次更新、可控)
缺点:需要数据库表
第二种「持久化 Token」(更安全能防盗用)——数据库存表(username、series(固定序列号)、token(每次用后更新)、last_used),Cookie 存 series+token。自动登录:用 series 查数据库拿 token、比对 Cookie 的 token、一致就登录并生成新 token 更新(token 每次变、series 不变)。盗用检测(核心优势):如果有人用旧的 token(同一 series) 登录(正常每次用后 token 更新旧的作废),说明 Cookie 被盗(真用户和盗用者各持一份、一个用了导致 token 更新另一个成旧的)→ 检测到 series 存在但 token 不匹配 = 盗用 → 删除整个 series、都要重新登录。优点:能检测盗用、能主动失效(删记录)、更安全;缺点:需数据库表。理解「持久化 Token:数据库存 series(固定)+token(每次更新)、Cookie 存 series+token、比对后更新 token;★盗用检测:旧 token(同 series)再用说明被盗→删整个 series 失效;优点检测盗用+主动失效+更安全、缺点需数据库表」,就掌握了持久化方式。
四、安全风险与防护
Remember-Me 的安全风险和防护措施:
核心风险:Remember-Me Cookie 是"长期有效的登录凭证"
一旦被窃取(XSS、网络抓包、物理接触设备)
攻击者就能长期冒充用户登录
(比 Session 更危险:Session 短、Remember-Me 长)
防护措施:
① HTTPS(必须):
防止网络抓包窃取 Cookie
② Cookie 设 HttpOnly:
防止 XSS 通过 JS 读取 Cookie(document.cookie 拿不到)
③ Cookie 设 Secure:
只在 HTTPS 下发送 Cookie
④ 用持久化 Token 方式:
能检测盗用(旧 token 再用 → 失效)、能主动失效
⑤ 合理的过期时间:
别设太长(如几个月),平衡便利和风险
⑥ 敏感操作要求重新认证:
改密码、支付、修改重要信息等
→ 不能只凭 Remember-Me,要重新输密码(re-authentication)
→ Spring Security 可区分"完全认证"和"Remember-Me 认证"
用 fullyAuthenticated 要求敏感操作走完全认证
区分认证强度(Spring Security):
isAuthenticated():包括 Remember-Me 自动登录的
isFullyAuthenticated():只有本次真正输密码登录的
→ 敏感操作要求 fullyAuthenticated(Remember-Me 不够)
所以 Remember-Me 要配合安全措施,别裸用
Remember-Me 的核心风险:Cookie 是长期有效凭证,被窃取(XSS/抓包)攻击者能长期冒充(比 Session 危险)。防护:① HTTPS(防抓包)、② HttpOnly(防 XSS 读 Cookie)、③ Secure(只 HTTPS 发送)、④ 用持久化 Token(检测盗用+主动失效)、⑤ 合理过期时间、⑥ 敏感操作要求重新认证(改密码/支付不能只凭 Remember-Me、要重新输密码)。Spring Security 区分 isAuthenticated()(含 Remember-Me)vs isFullyAuthenticated()(只本次真登录)——敏感操作要求 fullyAuthenticated。理解「风险:Cookie 是长期凭证被窃取能长期冒充;防护:HTTPS+HttpOnly+Secure+持久化 Token+合理过期+敏感操作重新认证(fullyAuthenticated)」,就掌握了安全风险和防护。
五、工作流程整合
把 Remember-Me 在 Spring Security 里的完整流程串起来:
Spring Security 的 Remember-Me 流程:
登录阶段(勾选了"记住我"):
1. 用户登录成功(正常认证)
2. RememberMeServices 生成 Remember-Me Token
- 简单方式:算哈希存 Cookie
- 持久化方式:数据库存 series+token,Cookie 存 series+token
后续请求(Session 已失效,但有 Remember-Me Cookie):
1. RememberMeAuthenticationFilter 拦截请求
2. 发现没有正常的认证(Session 没了)、但有 Remember-Me Cookie
3. RememberMeServices.autoLogin:
- 校验 Remember-Me Token(哈希比对 / 数据库比对)
- 通过 → 加载用户、创建 RememberMeAuthenticationToken
→ 放入 SecurityContext(自动登录成功)
- 不通过 → 不自动登录(要重新登录)
4. 之后请求就是"已登录"状态(Remember-Me 认证)
登出:
简单方式:清 Cookie(但 Token 本身还有效,服务端不存)
持久化方式:清 Cookie + 删数据库记录(真正失效)
所以 Remember-Me 是"正常登录之外的补充认证":
没有 Session 时,靠 Remember-Me Cookie 自动登录
Remember-Me 在 Spring Security 的流程:登录阶段(勾选记住我,登录成功后 RememberMeServices 生成 Token——简单方式算哈希存 Cookie、持久化方式存数据库+Cookie);后续请求(Session 失效但有 Remember-Me Cookie,RememberMeAuthenticationFilter 拦截 → RememberMeServices.autoLogin 校验 Token → 通过则加载用户创建 RememberMeAuthenticationToken 放入 SecurityContext 自动登录);登出(简单方式清 Cookie 但 Token 还有效、持久化方式清 Cookie+删数据库记录真正失效)。理解「流程:登录成功生成 Token(哈希 Cookie/数据库)、后续请求 RememberMeAuthenticationFilter 拦截+autoLogin 校验自动登录、登出简单方式清 Cookie(Token 还有效)持久化方式删数据库真失效」,就掌握了完整流程。
六、实践建议
总结 Remember-Me 的实践建议:
选择:
简单场景、内部系统、不太敏感 → 简单哈希 Token(无状态、简单)
安全敏感、需要盗用检测和主动失效 → 持久化 Token(更安全)
实践建议:
① 用 HTTPS(防 Cookie 被抓包)
② Cookie 设 HttpOnly + Secure
③ 安全敏感系统用持久化 Token(能检测盗用、能失效)
④ 过期时间合理(几天到几周,别几个月)
⑤ 敏感操作要求 fullyAuthenticated(Remember-Me 不够,要重新输密码)
⑥ 登出要真正失效(持久化方式删记录,简单方式只能清 Cookie)
⑦ 密钥(key)要保密且稳定(简单方式的哈希密钥)
常见误区:
✗ 只用简单方式又要求高安全(简单方式难失效、易被盗用)
✗ 不走 HTTPS(Cookie 被抓包 = 长期冒充)
✗ 敏感操作只凭 Remember-Me(应要求重新认证)
一句话:
Remember-Me = 长期凭证跨会话保持登录;
简单哈希无状态但难失效、持久化 Token 更安全能防盗用;
必须 HTTPS + HttpOnly,敏感操作要重新认证
Remember-Me 选择:简单场景/不敏感用简单哈希(无状态)、安全敏感/需盗用检测用持久化 Token(更安全)。实践:① HTTPS、② Cookie HttpOnly+Secure、③ 敏感系统用持久化 Token、④ 过期时间合理、⑤ 敏感操作要求 fullyAuthenticated、⑥ 登出真正失效、⑦ 密钥保密稳定。误区:只用简单方式又要求高安全、不走 HTTPS、敏感操作只凭 Remember-Me。理解「选择:不敏感用简单哈希、敏感用持久化 Token;实践:HTTPS+HttpOnly+持久化+合理过期+敏感操作 fullyAuthenticated+登出真失效」,就掌握了实践建议。
记忆钩子:「Remember-Me(记住我)=长期凭证(Cookie)跨会话保持登录(Session 过期也能自动登录);两种实现:①简单哈希 Token(TokenBased:Token=哈希(用户名+过期时间+密码+密钥)存 Cookie,重算哈希比对,无状态简单;缺点无法主动失效-登出没用只能改密码、安全性低)②持久化 Token(PersistentTokenBased:数据库存 series(固定)+token(每次用后更新),Cookie 存 series+token;★盗用检测:旧 token 同 series 再用=被盗→删整个 series 失效;能检测盗用+主动失效+更安全但需数据库表);★安全风险:Cookie 被窃取能长期冒充→必须 HTTPS+HttpOnly+Secure、敏感操作要求 fullyAuthenticated(Remember-Me 不够要重新输密码)」。
七、常见误区与追问
- 误区:Remember-Me 和 Session 一样安全。 更危险——Remember-Me Cookie 是长期有效的登录凭证(比 Session 长),一旦被窃取(XSS、抓包),攻击者能长期冒充用户;所以必须走 HTTPS、Cookie 设 HttpOnly+Secure,敏感操作还要求重新认证。
- 误区:简单哈希 Token 方式能主动让某个 Token 失效。 不能——简单方式的 Token 是自包含的哈希(服务端不存),用户登出也没用(Token 还有效),唯一能让它失效的是改密码(因为哈希里含密码);要主动失效得用持久化 Token 方式(删数据库记录)。
- 误区:持久化 Token 方式的 token 一直不变。 token 每次自动登录后都会更新(生成新 token 存数据库和 Cookie)、series 不变——正是靠「token 更新、旧 token 作废」来检测盗用:如果同一 series 的旧 token 被再次使用,说明 Cookie 被盗,就让整个 series 失效。
- 误区:有了 Remember-Me,敏感操作也能直接做。 不应该——Remember-Me 自动登录的安全性较低(凭一个 Cookie);敏感操作(改密码、支付、改重要信息)应该要求 fullyAuthenticated(本次真正输密码登录的),不能只凭 Remember-Me;Spring Security 用 isFullyAuthenticated() 区分。
- 追问:持久化 Token 方式怎么检测 Cookie 被盗用? 数据库存 series(固定)+ token(每次自动登录后更新),Cookie 也存这两个;正常每次用后 token 更新、旧 token 作废;如果检测到「某个 series 存在,但 Cookie 带的 token 和数据库存的不匹配」(即有人用了旧 token),说明这个 Cookie 被盗用了(真用户和盗用者各持一份,一个用了导致 token 更新、另一个就成了旧的);此时删除整个 series 的记录、让它失效,强制真用户和盗用者都重新登录。
- 追问:Remember-Me 和 Session 是什么关系? Remember-Me 是「正常 Session 登录之外的补充」——正常登录靠 Session;当 Session 过期或关闭浏览器丢失后,如果有 Remember-Me Cookie,RememberMeAuthenticationFilter 会用它自动登录(重新建立认证);所以 Remember-Me 让用户在没有有效 Session 时也能保持登录状态。
- 追问:为什么敏感操作要区分「完全认证」和「Remember-Me 认证」? 因为 Remember-Me 自动登录只凭一个 Cookie,安全性低于本次真正输密码登录;如果 Cookie 被盗,攻击者能自动登录,但不知道密码;所以敏感操作(改密码、支付)要求 isFullyAuthenticated()(本次真正输密码的完全认证),Remember-Me 认证不够——这样即使 Remember-Me Cookie 被盗,攻击者也做不了敏感操作(要重新输密码)。
八、加强记忆
Remember-Me(记住我)让用户「关闭浏览器、下次再来还保持登录」——即使 Session 过期,也能通过一个长期凭证(Cookie)自动登录。Spring Security 两种实现:① 简单哈希 Token(TokenBasedRememberMeServices)——Token = 哈希(用户名 + 过期时间 + 密码 + 密钥) 存 Cookie,下次带 Cookie 重新算哈希比对、一致就自动登录;无状态、简单(服务端不存),但缺点是无法主动失效(登出也没用、只能靠改密码,因哈希含密码)、安全性较低;② 持久化 Token(PersistentTokenBasedRememberMeServices)——数据库存 series(固定序列号)+ token(每次用后更新),Cookie 存 series+token,盗用检测:如果同一 series 的旧 token 被再次使用(说明 Cookie 被盗)→ 删除整个 series 失效;更安全(能检测盗用、能主动失效),但需数据库表。核心风险:Remember-Me Cookie 是长期凭证,被窃取能长期冒充(比 Session 危险)——防护:必须 HTTPS、Cookie 设 HttpOnly + Secure、安全敏感用持久化 Token、合理过期时间、敏感操作要求 fullyAuthenticated(改密码/支付不能只凭 Remember-Me、要重新输密码,Spring Security 用 isFullyAuthenticated() 区分)。一句话「Remember-Me=长期凭证 Cookie 跨会话保持登录;简单哈希 Token(用户名+过期+密码+密钥哈希,无状态但难失效登出没用只能改密码)、持久化 Token(数据库 series+token,token 每次更新,旧 token 再用=被盗→删 series 失效,更安全);Cookie 被窃取能长期冒充→必须 HTTPS+HttpOnly,敏感操作要 fullyAuthenticated 重新输密码」。