环境

操作系统:

Windows 10 x64

集成开发环境:

Spring Tool Suite 4 
Version: 4.14.0.RELEASE
Build Id: 202203131612

Postman(客户端):

Postman for Windows
Version 9.0.9
win32 10.0.19044 / x64

浏览器(客户端):

Google Chrome
版本 99.0.4844.84(正式版本) (64 位)

记住我功能

思路

项目结构

参考:

  1. Spring Security - 20 登录成功发送电子邮件提醒用户(基于 RabbitMQ 消息队列)
  2. Spring Boot - 使用 JdbcTemplate 判断一张 MySQL 表是否已经存在

在这里插入图片描述

编码

创建 JdbcTokenRepositoryImplConfiguration 配置类,提供一个 JdbcTokenRepositoryImpl 实现:

package com.mk.security.web.authentication.rememberme;

import java.sql.ResultSet;
import java.sql.SQLException;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.stereotype.Component;

@Component
public class JdbcTokenRepositoryImplConfiguration {
    
    @Autowired
    private DataSource dataSource;

    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    
    /**
     * JDBC based persistent login token repository implementation.
     *
     */
    @Bean
    public JdbcTokenRepositoryImpl JdbcTokenRepositoryImpl() {
        // 判断数据库中是否存在 persistent_logins 表
        String sql = "SHOW TABLES LIKE 'persistent_logins'";
        Boolean created = jdbcTemplate.query(sql, null, null, new ResultSetExtractor<Boolean>() {
            @Override
            public Boolean extractData(ResultSet rs) throws SQLException, DataAccessException {
                return rs.next(); // true if the new current row is valid; false if there are no more rows.
            }
        });
        
        JdbcTokenRepositoryImpl jdbcTokenRepositoryImpl = new JdbcTokenRepositoryImpl();
        jdbcTokenRepositoryImpl.setCreateTableOnStartup(!created);
        jdbcTokenRepositoryImpl.setDataSource(dataSource);
        return jdbcTokenRepositoryImpl;
    }
}

修改 WebSecurityConfigurer 配置类:

  1. 注入 JdbcTokenRepositoryImpl(第 22 ~ 23 行),并配置记住我功能(第 29 ~ 32 行);
  2. 注释 http.addFilterBefore(loginCaptchaValidationFilter, UsernamePasswordAuthenticationFilter.class); 语句(第 29 行),为了方便测试,暂时不使用验证码校验功能。
package com.mk.security.config.annotation.web.configuration;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;

import com.mk.security.web.authentication.filter.LoginCaptchaValidationFilter;

//@Configuration
@EnableWebSecurity
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {

    // 其他,保持不变
    
    @Autowired
    private JdbcTokenRepositoryImpl jdbcTokenRepositoryImpl;
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 其他,保持不变
        
//        http.addFilterBefore(loginCaptchaValidationFilter, UsernamePasswordAuthenticationFilter.class);
        
        http.rememberMe(customizer -> {
            customizer.tokenRepository(jdbcTokenRepositoryImpl);
            customizer.tokenValiditySeconds(60 * 60 * 24 * 7); // 7 days
        });
    }
}

测试

启动应用之后,由于我们使用了记住我功能,所以 Spring Security 会在数据库中创建一张 persistent_logins 表,该表用于保存那些使用记住我功能的用户的信息:

mysql> SHOW TABLES;
+-------------------------+
| Tables_in_hello-account |
+-------------------------+
| persistent_logins       |
| user                    |
+-------------------------+
2 rows in set (0.00 sec)

mysql> SELECT * FROM persistent_logins;
Empty set (0.01 sec)

现在,打开浏览器,访问 http://192.168.3.9:8080/login 填写正确的用户和密码,勾选 Remember me on this computer.,然后点击 Sign in 登录:

在这里插入图片描述

登录成功,可以看到多了一条 Cookie 信息 remember-me

在这里插入图片描述

在数据库中,保存了用户的记住我信息:

mysql> SELECT * FROM persistent_logins;
+----------+--------------------------+--------------------------+---------------------+
| username | series                   | token                    | last_used           |
+----------+--------------------------+--------------------------+---------------------+
| zs       | WMBHxhZ4CskfMmDwPMBIpA== | P4sydZc5LyCUI51dMX/e6w== | 2022-04-18 13:16:54 |
+----------+--------------------------+--------------------------+---------------------+
1 row in set (0.00 sec)

如果你关掉浏览器,重新打开。只要这条 Cookie 信息 remember-me 不过期,那么我们无须登录,就可以访问那些我们有权访问的资源。

但是,需要注意的是,如果你的记住我 Cookie 信息被盗用,恶意的访问者是能够冒充你的,如下演示:

在这里插入图片描述