SpringBoot整合SpringSecurity(四)记住我

实现“记住我” 功能 ,在用户登陆一次以后,系统会记住用户一段时间,在这段时间,用户不用反复登陆就可以使用我们的系统。

本文章代码可以参考 https://gitee.com/Maoxs/security-test 中的 security-rememberme

先说一下使用

改动

拿之前第的入门程序举例子

改一些login.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<h3>表单登录</h3>
<table>
<tr>
<td>用户名:</td>
<td><input type="text" name="username"></td>
</tr>
<tr>
<td>密码:</td>
<td><input type="password" name="password"></td>
</tr>
<tr>
<td>记住我:</td>
<td><input type="checkbox" name="remember-me"></td>
</tr>
<tr>
<td colspan="2">
<button type="button" onclick="login()">登录</button>
</td>
</tr>
</table>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<script>
function login() {
var username = $("input[name=username]").val();
var password = $("input[name=password]").val();
var rememberMe = $("input[name=remember-me]").val();
if (username === "" || password === "") {
alert("用户名或密码不能为空");
return;
}
$.ajax({
type: "POST",
url: "/authentication/form",
data: {
"username": username,
"password": password,
"remember-me": rememberMe,
},
success: function (e) {
console.log(e);
alert("登陆成功")
setTimeout(function () {
location.href = '/hello';
}, 500);
},
error: function (e,a,b) {
console.log(a);
console.log(b);
console.log(e.responseText);
alert("登陆失败zxczxczc")
}
});
}

</script>
</body>
</html>

在登陆页添加自动登录的选项,注意自动登录字段的 name 必须是 remember-me

两种方式

没的说 ,在WebSecurityConfigurerAdapter 中 的 configure() 方法添加一个 ``rememberMe() 就行了

@Override
protected void configure(HttpSecurity http) throws Exception {
//http.httpBasic() //httpBasic 登录
http.formLogin()
.loginPage("/login")
.loginProcessingUrl("/authentication/form") // 自定义登录路径
.failureHandler(failureAuthenticationHandler) // 自定义登录失败处理
.successHandler(successAuthenticationHandler) // 自定义登录成功处理
.and()
.logout()
.logoutUrl("/logout")
.and()
.authorizeRequests()// 对请求授权
.antMatchers("/login", "/authentication/require",
"/authentication/form").permitAll()
.anyRequest() // 任何请求
.authenticated()//; // 都需要身份认证
.and()
.rememberMe()
.rememberMeCookieName("remember")
.tokenValiditySeconds(3600)
.and()
.csrf().disable();// 禁用跨站攻击
}

然后在登陆的时候勾选一下,这时候登陆成功后,会在浏览器cookie里自动保存一条名为remember-me 的cookie如果你需要改变这个名字可以在 HttpSecurity 链上加入.rememberMeCookieName("remember") 即可。

数据库存储

使用 Cookie 存储虽然很方便,但是大家都知道 Cookie 毕竟是保存在客户端的,而且 Cookie 的值还与用户名、密码这些敏感数据相关,虽然加密了,但是将敏感信息存在客户端,毕竟不太安全。

Spring security 还提供了另一种相对更安全的实现机制:在客户端的 Cookie 中,仅保存一个无意义的加密串(与用户名、密码等敏感数据无关),然后在数据库中保存该加密串-用户信息的对应关系,自动登录时,用 Cookie 中的加密串,到数据库中验证,如果通过,自动登录才算通过。

代码实现

首先需要创建一张表来存储 token 信息:

CREATE TABLE `persistent_logins` (
`username` varchar(64) NOT NULL,
`series` varchar(64) NOT NULL,
`token` varchar(64) NOT NULL,
`last_used` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`series`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

然后创建一个配置类

@Configuration
public class RemberMeConfig {

private final DataSource dataSource;

public RemberMeConfig(DataSource dataSource) {
this.dataSource = dataSource;
}

@Bean
public PersistentTokenRepository persistentTokenRepository(){
JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
tokenRepository.setDataSource(dataSource);
// 如果token表不存在,使用下面语句可以初始化该表;若存在,请注释掉这条语句,否则会报错。
//tokenRepository.setCreateTableOnStartup(true);
return tokenRepository;
}
}

然后在 configure() 中稍微做修改

@Autowired
private PersistentTokenRepository persistentTokenRepository;

@Override
protected void configure(HttpSecurity http) throws Exception {
//http.httpBasic() //httpBasic 登录
http.formLogin()
.loginPage("/login")
.loginProcessingUrl("/authentication/form") // 自定义登录路径
.failureHandler(failureAuthenticationHandler) // 自定义登录失败处理
.successHandler(successAuthenticationHandler) // 自定义登录成功处理
.and()
.logout()
.logoutUrl("/logout")
.and()
.authorizeRequests()// 对请求授权
.antMatchers("/login", "/authentication/require",
"/authentication/form").permitAll()
.anyRequest() // 任何请求
.authenticated()//; // 都需要身份认证
.and()
.rememberMe()
.tokenRepository(persistentTokenRepository())
.tokenValiditySeconds(3600)
.userDetailsService(userDetailsService)
.and()
.csrf().disable();// 禁用跨站攻击
}

然后运行程序 勾选自动登录后,Cookie 和数据库中均存储了 token 信息。

基本原理

以前我们说过整个认证的流程,这个流程大概是这样的

  1. UsernamePasswordAuthenticationFilter 认证成功后会走successfulAuthentication 方法
  2. 然后经过successfulAuthenticationRememberMeService 其中有个 TokenRepository
  3. TokenRepository 生成token,首先将 token 写入到浏览器的 Cookie 中,然后将 token、认证成功的用户名写入到数据库中
  4. 下次请求时,会经过RememberMeAuthenticationFilter 它会读取 Cookie 中的 token,交给 RememberMeService 从数据库中查询记录
  5. 如果存在记录,会读取用户名并去调用 UserDetailsService,获取用户信息,并将用户信息放入Spring Security 中,实现自动登陆。


注意的是,过滤连在怎么玩都是链式的 由此我们可以知道

RememberMeAuthenticationFilter 在整个过滤器链中是比较靠后的位置,也就是说在传统登录方式都无法登录的情况下才会使用自动登陆。

本博文是基于springboot2.x 和security 5 如果有什么不对的请在下方留言。