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

  1. 用户登录:用户成功登录时,如果勾选了“记住我”,Spring Security 会生成一个 Remember-Me Cookie 并写入用户浏览器。
  2. 后续请求:当用户再次访问应用时,即使没有 Session(Session 可能已过期),浏览器也会自动携带这个 Cookie
  3. 过滤器拦截Spring SecurityRememberMeAuthenticationFilter 会拦截该请求,从 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. 生成令牌:用户登录并选择“记住我”时,系统生成一个随机的 seriestoken,将 (username, series, token, last_used) 存入数据库,并将seriestoken 组合成Cookie 发送给浏览器(例如:base64(series + ":" + token)
  2. 验证令牌
    • 浏览器携带 Cookie 访问。
    • 过滤器解析出 seriestoken
    • 根据 series 去数据库查询记录。
    • 比较数据库中的 tokenCookie 中的 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:配置 CookieHttpOnly,防止 XSS 攻击窃取 Cookie
  4. 合理的有效期:不要设置过长的 tokenValiditySeconds,在便利性和安全性之间取得平衡。
  5. 敏感操作需重新认证:对于修改密码、更改邮箱等敏感操作,即使有 Remember-Me Cookie,也应要求用户重新输入密码。
  6. 登出时清除:确保在用户主动登出时,调用 logout 方法清除 Remember-Me Cookie 和数据库记录(对于持久化方式)。

总结

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

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

Logo

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

更多推荐