
在 Web 应用程序中,“记住我”功能是一项非常实用的功能,它允许用户在下次访问网站时无需再次输入用户名和密码。Spring Security 提供了对“记住我”功能的支持,并且可以通过不同的方式来存储记住我的令牌。本文将详细介绍如何在 Spring 应用中实现“记住我”功能,并使用不同的令牌存储方式。
“记住我”功能的核心原理是在用户登录时生成一个令牌(通常是一个加密的字符串),并将其存储在客户端(通常是 Cookie)和服务器端。当用户下次访问网站时,客户端会将该令牌发送给服务器,服务器验证该令牌的有效性,如果有效则自动为用户进行登录。
Spring Security 提供了 RememberMeServices 接口来处理记住我功能。默认情况下,Spring Security 提供了两种令牌存储方式:
InMemoryTokenRepositoryImpl:将令牌存储在内存中,适用于开发和测试环境,但不适合生产环境,因为服务器重启后令牌会丢失。PersistentTokenRepository:将令牌持久化存储在数据库中,适用于生产环境。首先,我们需要在 Spring Security 配置中启用记住我功能。以下是一个简单的配置示例:
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.configuration.EnableWebSecurity;import org.springframework.security.web.SecurityFilterChain;@Configuration@EnableWebSecuritypublic class SecurityConfig {@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().rememberMe().key("uniqueAndSecret").tokenValiditySeconds(86400); // 令牌有效期为 24 小时return http.build();}}
在上述配置中,我们使用 rememberMe() 方法启用了记住我功能,并设置了一个唯一的密钥 key 和令牌的有效期 tokenValiditySeconds。
InMemoryTokenRepositoryImpl 是 Spring Security 提供的一个简单的令牌存储实现,它将令牌存储在内存中。以下是如何使用它的示例:
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.configuration.EnableWebSecurity;import org.springframework.security.web.SecurityFilterChain;import org.springframework.security.web.authentication.rememberme.InMemoryTokenRepositoryImpl;@Configuration@EnableWebSecuritypublic class SecurityConfig {@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {InMemoryTokenRepositoryImpl tokenRepository = new InMemoryTokenRepositoryImpl();http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().rememberMe().key("uniqueAndSecret").tokenValiditySeconds(86400).tokenRepository(tokenRepository);return http.build();}}
PersistentTokenRepository 是一个接口,Spring Security 提供了一个默认的实现 JdbcTokenRepositoryImpl,它将令牌存储在数据库中。以下是如何使用它的示例:
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.configuration.EnableWebSecurity;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;@Configuration@EnableWebSecuritypublic class SecurityConfig {private final DataSource dataSource;public SecurityConfig(DataSource dataSource) {this.dataSource = dataSource;}@Beanpublic PersistentTokenRepository persistentTokenRepository() {JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();tokenRepository.setDataSource(dataSource);// 如果数据库中没有相应的表,可以使用以下方法自动创建// tokenRepository.setCreateTableOnStartup(true);return tokenRepository;}@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().rememberMe().key("uniqueAndSecret").tokenValiditySeconds(86400).tokenRepository(persistentTokenRepository());return http.build();}}
JdbcTokenRepositoryImpl 需要一个特定的数据库表来存储令牌。以下是创建该表的 SQL 语句:
CREATE TABLE persistent_logins (username VARCHAR(64) NOT NULL,series VARCHAR(64) PRIMARY KEY,token VARCHAR(64) NOT NULL,last_used TIMESTAMP NOT NULL);
| 令牌存储方式 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
InMemoryTokenRepositoryImpl |
开发和测试环境 | 简单易用,无需数据库 | 服务器重启后令牌丢失,不适合生产环境 |
JdbcTokenRepositoryImpl |
生产环境 | 令牌持久化存储,服务器重启后令牌不会丢失 | 需要配置数据库,相对复杂 |
通过上述示例,我们可以看到在 Spring 应用中实现“记住我”功能并存储令牌是非常简单的。根据不同的应用场景,我们可以选择合适的令牌存储方式。