本章将介绍如何使用 Spring Security6 快速实现“记住我”功能,避免用户每次登陆都需要输入用户名/密码。
登录时的 “记住我” 功能是一种常见的用户体验优化措施。用户在登录界面输入用户名和密码后,勾选 “记住我” 选项再进行登录。之后,即使关闭浏览器或退出应用程序,下次访问登录页面时,系统会自动填充用户名,甚至可能自动完成登录,无需用户再次输入用户名和密码。例如:
通常借助 Cookie、令牌(Token)等技术实现。用户登录成功后,服务器生成包含用户身份信息的 Cookie 或 Token,发送给客户端存储。客户端下次发起请求时,会自动携带该 Cookie 或 Token,服务器验证其有效性,若有效则识别用户身份,实现自动登录。
提升用户体验:为用户节省时间和精力,尤其在频繁访问的网站或应用中,无需反复输入用户名和密码,提高登录效率。
增强用户粘性:使登录过程更便捷,增加用户使用频率,进而提高用户对应用的依赖度和忠诚度。
防止遗忘:对常使用同一设备登录的用户,可避免遗忘用户名或密码的困扰。
社交媒体平台:如微信、微博等,用户日常频繁登录,“记住我” 功能让他们快速进入应用,查看信息、发布动态。
电子商务网站:像淘宝、京东,用户购物时需登录,勾选 “记住我” 后,下次购物登录更便捷,促进交易。
企业内部系统:员工每天访问公司内部办公系统,“记住我” 功能减少登录时间,提高工作效率。
限制存储内容:绝不存储明文密码,可存储加密后的密码或唯一标识用户的令牌。令牌应定期更新,降低被破解风险。
设置合理有效期:根据应用安全需求和用户使用习惯,设置 Cookie 或 Token 的有效期限,如 7 天、1 个月等。到期后,用户需重新登录,增强安全性。
防范窃取风险:采用安全的传输协议(如 HTTPS),防止数据在传输过程中被窃取或篡改。同时,对存储的用户信息加密处理,即使数据被窃取,攻击者也难以获取有效信息。
考虑设备安全性:在公用设备上,建议用户不勾选 “记住我”。若已勾选,使用完毕后应及时退出登录或清除 Cookie,防止他人获取用户登录信息。
在 Spring Security6 中实现 "记住我" 功能非常直观,主要通过配置 rememberMe() 方法来完成。
注意:下面示例将基于 Spring Security6 数据库认证 章节进行演示。
依赖主要添加 mybatis 和 mysql 依赖,在 Spring Security6 数据库认证 章节已经添加,数据库表如下图:
注意:persistent_logins 表是 remember-me 功能自动添加的。
在 SecurityConfig.java 配置类中,添加 DataSource(数据源)、PersistentTokenRepository(Token 持久化)和 rememberMe() 配置,如下:
package com.hxstrive.spring_security.config; import com.hxstrive.spring_security.service.UserService; import jakarta.annotation.Resource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl; import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository; import javax.sql.DataSource; import static org.springframework.security.config.Customizer.withDefaults; /** * Spring Security 配置类 * @author hxstrive.com */ @Configuration public class SecurityConfig { @Resource private UserService userService; @Autowired private DataSource dataSource; // 持久化对象 @Bean public PersistentTokenRepository getPersistentTokenRepository() { JdbcTokenRepositoryImpl jdbcTokenRepositoryImpl=new JdbcTokenRepositoryImpl(); jdbcTokenRepositoryImpl.setDataSource(dataSource); // 自动建表,第一次启动时需要,第二次启动时注释掉 jdbcTokenRepositoryImpl.setCreateTableOnStartup(true); return jdbcTokenRepositoryImpl; } @Bean public UserDetailsService userDetailsService() { return new CustomUserDetailsService(this.userService); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { System.out.println("securityFilterChain()"); // csrf 设置 // csrfCustomizer.ignoringRequestMatchers(*) 表示所有请求地址都不使用 csrf http.csrf(csrfCustomizer -> csrfCustomizer.ignoringRequestMatchers("*")) // authorize.anyRequest().authenticated() 表示所有的请求都需要进行身份验证。 .authorizeHttpRequests(authorize -> authorize.anyRequest().authenticated()) // formLogin() 用于配置表单登录功能 .formLogin(e -> e.loginProcessingUrl("/login") // 配置处理登陆请求到地址 // 自定义登陆页面地址 .loginPage("/view/login") // 自定义登陆成功页面 .defaultSuccessUrl("/view/success", true) // 登陆成功,重定向地址 // 登陆失败页面 .failureUrl("/view/login?error") // 登陆失败,重定向地址 .permitAll()) // remember me .rememberMe(rememberMeCustomizer -> rememberMeCustomizer .userDetailsService(userDetailsService()) //登录逻辑交给哪个对象 .tokenRepository(getPersistentTokenRepository())) //持久层对象 // httpBasic() 启用 HTTP 基本认证,withDefaults() 表示使用默认的 HTTP 基本认证配置。 .httpBasic(withDefaults()); return http.build(); } }
基于已经存在的 login.html 页面,在登陆表单中添加“记住我”复选框,如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>自定义登录</title> </head> <body> <h1>自定义登录页面</h1> <!-- 显示错误信息 --> <div id="errorInfo" style="color:red;display:none;">用户名/密码错误</div> <script> if(location.href.indexOf("?error") !== -1) { document.getElementById("errorInfo").style.display = "block"; } </script> <form action="/login" method="post"> <p> 用户名:<input type="text" name="username" placeholder="请输入用户名" /> </p> <p> 密码:<input type="password" name="password" placeholder="请输入密码" /> </p> <p> <input type="checkbox" name="remember-me"> 记住我 </p> <p> <input type="submit" value="登录"/> </p> </form> </body> </html>
关键代码“<input type="checkbox" name="remember-me"> 记住我”,注意,name 需要配置为“remember-me”。
在 IDE 中运行 Spring Boot 应用程序的主类(通常是带有 @SpringBootApplication 注解的类),监听 8080 端口。
打开浏览器,访问 http://localhost:8080/hello 地址,由于所有请求都需要身份验证,你会看到自定义的登录页面,如下图:
上图,多了一个“记住我”复选框,输入正确到用户名和密码且勾选“记住我”复选框,点击“登陆”,成功登陆,如下图:
从上图可知,登陆成功后,自动创建了名为 remember-me 的 Cookie,再去看看数据,如下图:
Spring Security “记住我”功能,默认有效时间是两周,可以通过设置状态有效时间,即使项目重新启动下次也可以正常登录。如下:
// remember me .rememberMe(rememberMeCustomizer -> rememberMeCustomizer .userDetailsService(userDetailsService()) //登录逻辑交给哪个对象 .tokenRepository(getPersistentTokenRepository()) //持久层对象 .tokenValiditySeconds(120) // 单位秒 )
方法源码如下:
注意:具有 remember-me 功能时,千万不要使用登录成功转发,应该使用重定向,否则,刷新浏览器会在数据库中出现多条用户登录信息数据。