一、Remember-Me 的整体工作流程

  1. 用户登录:用户成功登录时,如果勾选了“记住我”,Spring Security 会生成一个 Remember-Me Cookie 并写入用户浏览器。
  2. 后续请求:当用户再次访问应用时,即使没有 Session(Session 可能已过期),浏览器也会自动携带这个 Cookie。
  3. 过滤器拦截:Spring Security 的 RememberMeAuthenticationFilter 会拦截该请求,从 Cookie 中解析出令牌信息。
  4. 身份验证:过滤器根据令牌信息(可能是哈希值或系列标识符)去验证其有效性,并加载对应的用户信息(UserDetails)。
  5. 创建认证对象:验证成功后,创建一个 RememberMeAuthenticationToken 对象,并将其设置到 SecurityContextHolder 中,表示用户已被“记住”并自动登录。
  6. 访问资源:用户可以正常访问受保护的资源。

二、实现方式一:简单哈希(Simple Hash)

1.原理

这种方式不需要服务器端存储。它通过以下信息生成一个哈希值作为令牌:用户名 、密码(或密码的哈希值)、过期时间、一个私有密钥(在应用中配置)。
生成的令牌格式通常为:base64(username + ":" + expirationTime + ":" + md5Hex(username + ":" + expirationTime + ":" + password + ":" + key))。

2.配置方式

在 Spring Security 配置类中启用:

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
        .rememberMe(rememberMe -> rememberMe
            .key("uniqueAndSecretKey") // 设置私有密钥,必须足够复杂且保密
            .rememberMeCookieName("my-remember-me-cookie") // 自定义Cookie名称
            .rememberMeParameter("remember-me") // 自定义登录表单中的参数名
            .tokenValiditySeconds(3600 * 24 * 7) // 设置令牌有效期(秒),默认两周
            .useSecureCookie(true) // 仅在HTTPS下传输Cookie
        );
    return http.build();
}

3. 优缺点

  • 优点:实现简单,无需额外存储。
  • 缺点:
    • 安全性较低:令牌中包含了用户名和过期时间(虽然是Base64编码,但可轻易解码)。如果密钥泄露,攻击者可以伪造令牌。
    • 无法撤销:一旦令牌生成,在过期前一直有效。如果用户修改了密码,旧的令牌依然有效,存在安全风险。
    • 依赖密码:如果用户修改了密码,所有之前生成的记住我令牌都会失效(因为密码变了,哈希值对不上),这既是缺点也是一种安全特性。

三、实现方式二:持久化令牌(Persistent Token)

这种方式更安全、更灵活,是推荐的生产环境使用方式。它需要将令牌信息存储在数据库中。

1. 核心概念

它使用一个系列(Series) 的概念。一个系列对应一个用户的一组令牌。

  • 系列标识符(Series Identifier):一个随机生成的、长期有效的字符串,用于标识一组令牌。它被存储在 Cookie 中。
  • 令牌值(Token Value):一个随机生成的、短期有效的字符串,用于实际验证身份。它存储在数据库中,并且每次请求后都会更换(轮换),以防止重放攻击。

数据库表结构

需要创建一张表来存储令牌,Spring Security 提供了标准的建表语句:

create table persistent_logins (
    username varchar(64) not null,
    series varchar(64) primary key,
    token varchar(64) not null,
    last_used timestamp not null
);

  • sername: 用户名
  • series: 系列标识符(Cookie中存储的值)
  • token: 当前有效的令牌值
  • last_used: 最后一次使用的时间

3. 工作流程

  1. 生成令牌:用户登录并选择“记住我”时,系统生成一个随机的 series 和 token,将 (username, series, token, last_used) 存入数据库,并将series 和 token 组合成Cookie 发送给浏览器(例如:base64(series + ":" + token)。
  2. 验证令牌:
    • 浏览器携带 Cookie 访问。
    • 过滤器解析出 series 和 token。
    • 根据 series 去数据库查询记录。
    • 比较数据库中的 token 和 Cookie 中的 token 是否匹配。
    • 如果匹配,验证通过,并生成一个新的随机 token 更新到数据库和 Cookie 中(令牌轮换),更新 last_used 时间。
    • 如果不匹配,说明令牌可能已被盗用,验证失败,并删除该系列的所有记录(撤销登录)。
  3. 撤销登录:当用户主动登出、修改密码或管理员强制下线时,只需根据 username 或 series 删除数据库中的记录即可,所有记住我的会话立即失效。

4. 配置方式

  • 添加依赖(如使用JDBC)
  • 配置数据源和 JdbcTemplate
  • 配置 Remember-Me 服务
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
        .rememberMe(rememberMe -> rememberMe
            .key("uniqueAndSecretKey") // 仍然需要一个密钥用于签名Cookie,防止篡改
            .tokenRepository(persistentTokenRepository()) // 注入持久化令牌仓库
            .tokenValiditySeconds(3600 * 24 * 7) // 令牌有效期
            .userDetailsService(userDetailsService) // 必须提供UserDetailsService
        );
    return http.build();
}

@Bean
public PersistentTokenRepository persistentTokenRepository() {
    JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
    tokenRepository.setDataSource(dataSource);
    // 如果表不存在,启动时自动创建(生产环境建议手动建表)
    tokenRepository.setCreateTableOnStartup(true);
    return tokenRepository;
}

5.优缺点

  • 优点:
    • 安全性高:令牌是随机生成的,不包含用户敏感信息。
    • 支持撤销:可以主动使令牌失效。
    • 防止重放攻击:通过令牌轮换机制,即使令牌被截获,攻击者也只能使用一次。
  • 缺点:需要额外的数据库存储和维护。

四、核心组件解析

  1. RememberMeAuthenticationFilter
  • 继承自 OncePerRequestFilter,在 doFilter 方法中工作。
  • 它从请求中读取 Remember-Me Cookie。
  • 调用配置的 RememberMeServices(如 PersistentTokenBasedRememberMeServices)的 autoLogin 方法进行验证。
  • 验证成功后,创建 RememberMeAuthenticationToken 并设置到 SecurityContextHolder。
  1. RememberMeServices 接口
  • 定义了 Remember-Me 的核心服务方法:autoLogin(自动登录)、loginFail(登录失败处理)、loginSuccess(登录成功处理)、logout(登出处理)。
  • 主要实现类:
    • TokenBasedRememberMeServices:简单哈希方式的实现。
    • PersistentTokenBasedRememberMeServices:持久化令牌方式的实现。
  1. RememberMeAuthenticationProvide
  • 一个认证提供者,负责处理 RememberMeAuthenticationToken 类型的认证。
  • 一个认证提供者,负责处理 RememberMeAuthenticationToken 类型的认证。

五、安全注意事项

  1. 密钥安全:key(私有密钥)必须足够长、随机且严格保密。如果泄露,攻击者可以伪造任意用户的记住我令牌。
  2. 使用 HTTPS:务必配置 useSecureCookie(true),确保 Cookie 仅通过 HTTPS 传输,防止中间人攻击窃取 Cookie。
  3. 设置 HttpOnly:配置 Cookie 为 HttpOnly,防止 XSS 攻击窃取 Cookie。
  4. 合理的有效期:不要设置过长的 tokenValiditySeconds,在便利性和安全性之间取得平衡。
  5. 敏感操作需重新认证:对于修改密码、更改邮箱等敏感操作,即使有 Remember-Me Cookie,也应要求用户重新输入密码。
  6. 登出时清除:确保在用户主动登出时,调用 logout 方法清除 Remember-Me Cookie 和数据库记录(对于持久化方式)。

总结

特性简单哈希 (Simple Hash)持久化令牌 (Persistent Token)
存储无状态,无需存储需要数据库存储令牌
安全性较低,依赖密钥保密较高,令牌随机且可轮换
撤销能力无,无法主动撤销有,可删除数据库记录
实现复杂度简单中等,需建表和配置
适用场景简单应用,对安全要求不高生产环境推荐,对安全要求高

在实际项目中,强烈建议使用持久化令牌(Persistent Token)方式,并配合 HTTPS、合理的密钥管理和令牌有效期,以在保证用户体验的同时,最大程度地保障应用安全。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐