第7章 Spring Security RememberMe

本文详细解读了如何在Spring Security中实现RememberMe功能,通过数据库存储token,配置过程和涉及的关键类,包括`JdbcTokenRepository`、`RememberMeAuthenticationFilter`和`RememberMeServices`。阅读者将了解登录流程和如何配置`HttpSecurity`以支持无痕登录。

在许多应用软件我们都可以看到 RememberMe 选项。如果我们勾选的话,下次访问就无须再登录。那么Spring Security是怎么实现 RememberMe 功能呢?我们基于数据库存储token的方式来实现 RememberMe

RememberMe配置

这里配置是针对第4章改造的。这里主要替换 UserWebSecurityConfigconfigure 方法

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        .authorizeRequests()
        .antMatchers("/admin/**").hasAnyRole("ADMIN")
        .antMatchers("/hello2/").fullyAuthenticated()
        .antMatchers("/rememberMe/").rememberMe()
        .anyRequest().authenticated()
        .and()
        .formLogin()
        .permitAll()
        .and()
        .rememberMe()
        .tokenRepository(jdbcTokenRepository()).and()
        .csrf().disable();
}

// 新增
@Autowired
private DataSource dataSource;

// 新增
public JdbcTokenRepositoryImpl jdbcTokenRepository() {
    JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
    jdbcTokenRepository.setDataSource(dataSource);
    return jdbcTokenRepository;
}
数据库表

我们需要新增 persistent_logins 表。表的创建语句在 JdbcTokenRepositoryImpl 已经内置。

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

至此,RememberMe 功能就开发完成了。那么它是如何实现呢?

原理解析
涉及到的类

这里主要涉及到了 RememberMeAuthenticationFilter 过滤器 、 RememberMeServices 接口和他的实现类。

在这里插入图片描述

从登录地方开始

为什么我们勾选了就可以了,所以我们从登录地方开始追踪。我们可以发现 AbstractAuthenticationProcessingFilter 类完成登录时,我们会调用该类的 successfulAuthentication() 方法,然后这个方法又会调用 rememberMeServices#loginSuccess 方法进行处理。

rememberMeServices#loginSuccess 是实现 RememberMe 功能的入口。主要逻辑是,首先判断请求是否有 remember-me 参数并且该参数的值必须是 (true、on、yes、1)的其中一项。然后进入 AbstractRememberMeServices#onLoginSuccess 的方法,生成Token,存储Token到数据库,最后把Token写入 response.addCookie(cookie)

在这里插入图片描述

RememberMeAuthenticationFilter 过滤器

上面登录成功后,就算我们重新打开浏览器,再次访问也无须进行登录。这是怎么做到呢?大致实现时序图如下所示。

在这里插入图片描述
备注
本系列都是学习《深入浅出Spring Security》的笔记

Spring Boot 4.0.0(基于 Spring Framework 6 和 Jakarta EE)中配置 `SecurityConfig` 并启用 `remember-me` 功能,使用 JPA 管理用户和持久化 remember-me 的令牌(即“记住我”功能的数据库储),需要以下几个步骤: 1. 添加依赖 2. 创建实体类(User) 3. 创建 UserRepository 4. 配置 SecurityConfig 5. 配置数据源和 JPA 6. 启用 Remember-Me 使用 JdbcTokenRepository --- ### ✅ 1. Maven 依赖(pom.xml) 确保你已添加以下依赖: ```xml <dependencies> <!-- Spring Boot Starter Web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Spring Boot Starter Security --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <!-- Spring Boot Starter Data JPA --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <!-- Database Driver (e.g., H2 or MySQL) --> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> <!-- 或者使用 MySQL --> <!-- <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency> --> </dependencies> ``` --- ### ✅ 2. 用户实体类(User) ```java package com.example.demo.entity; import jakarta.persistence.*; @Entity @Table(name = "users") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(unique = true, nullable = false) private String username; @Column(nullable = false) private String password; @Column(nullable = false) private String role; // Getters and Setters public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getRole() { return role; } public void setRole(String role) { this.role = role; } } ``` --- ### ✅ 3. UserRepository ```java package com.example.demo.repository; import com.example.demo.entity.User; import org.springframework.data.jpa.repository.JpaRepository; public interface UserRepository extends JpaRepository<User, Long> { User findByUsername(String username); } ``` --- ### ✅ 4. 自定义 UserDetailsService ```java package com.example.demo.service; import com.example.demo.entity.User; import com.example.demo.repository.UserRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import java.util.Collection; import java.util.Collections; @Service public class CustomUserDetailsService implements UserDetailsService { @Autowired private UserRepository userRepository; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userRepository.findByUsername(username); if (user == null) { throw new UsernameNotFoundException("User not found: " + username); } return new org.springframework.security.core.userdetails.User( user.getUsername(), user.getPassword(), getAuthorities(user) ); } private Collection<? extends GrantedAuthority> getAuthorities(User user) { return Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + user.getRole())); } } ``` --- ### ✅ 5. SecurityConfig 配置(启用 rememberMe) ```java package com.example.demo.config; 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.configuration.EnableWebSecurity; 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; @Configuration @EnableWebSecurity public class SecurityConfig { @Autowired private UserDetailsService userDetailsService; @Autowired private DataSource dataSource; // Required for JdbcTokenRepository @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(authz -> authz .requestMatchers("/login", "/register", "/h2-console/**").permitAll() .anyRequest().authenticated() ) .formLogin(form -> form .loginPage("/login") .defaultSuccessUrl("/home") .permitAll() ) .rememberMe(remember -> remember .userDetailsService(userDetailsService) .tokenRepository(persistentTokenRepository()) .tokenValiditySeconds(86400) // 24 hours .rememberMeParameter("remember-me") // name of checkbox in login form ) .logout(logout -> logout .logoutSuccessUrl("/login?logout") .permitAll() ) .csrf(csrf -> csrf .ignoringRequestMatchers("/h2-console/**") ) .headers(headers -> headers .frameOptions().sameOrigin() // For H2 Console ); return http.build(); } @Bean public PersistentTokenRepository persistentTokenRepository() { JdbcTokenRepositoryImpl repo = new JdbcTokenRepositoryImpl(); repo.setDataSource(dataSource); // 可选:第一次运行时自动创建表(只需执行一次) // repo.setCreateTableOnStartup(true); return repo; } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } } ``` --- ### ✅ 6. application.yml 配置示例(使用 H2 数据库) ```yaml spring: datasource: url: jdbc:h2:mem:securitydb driver-class-name: org.h2.Driver username: sa password: jpa: hibernate: use-new-id-generator-mappings: false ddl-auto: create-drop show-sql: true properties: hibernate: format_sql: true h2: console: enabled: true ``` --- ### ✅ 7. 创建 Remember-Me 表(Spring Security 默认表名) 如果你**没有**设置 `repo.setCreateTableOnStartup(true)`,请手动创建表: ```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 ); ``` > 表名必须是 `persistent_logins`,字段也需一致,否则会出错。 --- ### ✅ 8. 登录页面示例(Thymeleaf) ```html <!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <title>Login</title> </head> <body> <h2>Login</h2> <form th:action="@{/login}" method="post"> <div> <label>Username: <input type="text" name="username"/></label> </div> <div> <label>Password: <input type="password" name="password"/></label> </div> <div> <label> <input type="checkbox" name="remember-me"/> Remember me </label> </div> <button type="submit">Login</button> </form> </body> </html> ``` --- ### 🔍 解释说明 - `PersistentTokenRepository` 使用 `JdbcTokenRepositoryImpl` 将 remember-me 令牌保数据库。 - `remember-me` 参数对应登录表单中的 checkbox 名称。 - `tokenValiditySeconds(86400)` 设置记住我有效期为 24 小时。 - `userDetailsService` 必须提供给 remember-me,用于重建认证信息。 - 使用 `BCryptPasswordEncoder` 安全加密密码。 - H2 数据库仅用于演示,生产环境可替换为 MySQL/PostgreSQL。 --- ### ✅ 测试流程 1. 启动应用 2. 访问 `/login` 3. 勾选 “Remember me” 4. 登录后关闭浏览器,重新打开仍保持登录状态(24 小时内) ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值