1.引言
Spring Security是一个功能强大的安全框架,用于保护Spring应用程序中的资源。其中一个常见的功能是“记住我”,即在用户关闭浏览器后仍能保持登录状态。本教程将介绍如何配置和使用Spring Security实现“记住我”功能,帮助开发者构建更加安全可靠的Web应用程序。
本章的代码实现是在上一篇教程:精通Spring Boot: Spring Security 整合验证码登录基础上,如果感觉本篇跳跃幅度较大,可先阅读上一篇,或访问我的github.com(文末会附上地址),下载源码阅读。
2.基本原理介绍
Spring Security记住我功能的基本原理:
首先当我们的浏览器发送请求到UsernamePasswordAuthenticationFilter时,如果认证成功的话,会调用一个RememberMeService服务, 在RememberMeService中,存在一个TokenRepository,它会将token写入到浏览器的cookie中,并将token同时写入到数据库。当用户在下一次重新登录时,服务请求会经过RememberMeAuthentionFilter,这个filter会读取浏览器Cookie中的token,然后再传递给RememberMeService, RememberMeService再去数据库中查询该token是否有效,若该token有效,则会取出对应的用户名,再去调用UserDetailsService获取用户信息,再将用户信息放入SecurityContext里面。
RememberMeAuthentionFilter
在Spring Security过滤器链中处于AuthenticationFileter中的最后,当其他验证方式无法验证用户信息时,才会调用RememberMeAuthentionFilter
类来验证用户信息。
3.如何实现记住我?
要实现记住我功能,在Spring Security中操作比较简单,只需要实例化PersistentTokenRepository,然后在配置中增加rememberMe配置就行,具体代码实现如下:
import com.linking.springsecurityremeberme.filter.CaptchaFilter;
import com.linking.springsecurityremeberme.handler.MyFailureHandler;
import com.linking.springsecurityremeberme.service.impl.UserDetailsServiceImpl;
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.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import javax.sql.DataSource;
/**
* @author developlee
* @since 2019/1/18 15:30
*/
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsServiceImpl userDetailsService;
@Autowired
private DataSource dataSource;
@Bean
public PersistentTokenRepository tokenRepository() {
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);
// 设置为true,则项目启动时,会在对应数据源中自动建表token表
jdbcTokenRepository.setCreateTableOnStartup(false);
return jdbcTokenRepository;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Autowired
private AppConfig appConfig;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin().loginPage("/sign_in").loginProcessingUrl(appConfig.getLoginUri())
.defaultSuccessUrl("/welcome").permitAll()
.failureHandler(new MyFailureHandler())
.and().authorizeRequests().antMatchers("/code/image").permitAll()
.and().addFilterBefore(new CaptchaFilter(appConfig, new MyFailureHandler()), UsernamePasswordAuthenticationFilter.class)
.logout().logoutUrl("/auth/logout").clearAuthentication(true)
.and().rememberMe().tokenRepository(tokenRepository())//设置tokenRepository
.alwaysRemember(true) // 总是记住,会刷新过期时间
.tokenValiditySeconds(300)// 设置过期时间为5分钟
.userDetailsService(userDetailsService) // 设置userDetailsService,用来获取username
.and().authorizeRequests().anyRequest().authenticated();
}
}
在页面中,新增记住我
<span><input name="rememberMe" type="checkbox" th:value="true">记住我</span>
4.测试功能
经过上面的步骤,我们基本上已经完成了如何添加记住我功能,接下来我们在页面上输入用户名和密码,并同时点击记住我。登录成功后,会跳转至welcome.html页。然后重启系统,清空服务器session, 再次去访问welcome.html,看看能否直接访问。
点击登录,已经成功跳转到welcome.html,重启清空session,直接在地址输入http:localhost:8080/welcome.html 直接能够访问,说明在请求在经过Spring Security过滤器链时,读取了cookies中的token,并通过token去查找了用户的信息,再通过userDetailsService进行了登录。
5.记住我源码分析
OK,我们先从第一步登录操作开始说起,第一次登录时,必然会经过UsernamePasswordAuthenticationFilter,所以把断点设置在该类中
然后接着代码走走走,下一步来到successfulAuthentication这个方法,也就是登录成功后的处理方法,可以看到这里rememberService会将登录成功的用户信息保存
OK,再接着走,看下这个rememberService.onLoginSuccess具体实现
可以看到,这个方法,会将一条token记录插入到数据库中,并写入到Cookie。到这里为止,登录操作基本已经完成。
接下来重新启动下服务,并直接访问welcome.html,看看代码的执行顺序如何。断点要打在RememberMeService中的autoLogin方法。
看下实现,首先是判断cookie是否为null和为空,接着往下走,来到decodeCookie解析cookie,然后通过processAutoLoginCookie这个方法,去数据库中通过cookieToken查询用户信息。
接下来再对用户信息进行校验(用户更换了密码之类的情况),check成功后,再执行登录操作,写入用户信息到SecurityContext中。
以下是processAutoLoginCookie
方法的具体实现.
在Spring Security中实现记住我功能有两种实现,一种是上面介绍的PersistentTokenBasedRememberMeService,一种是TokenBasedRememberService。
这两种的区别主要在于什么呢?
它们是AbstractRememberMeServices.onLoginSuccess
方法的不同实现。
上面介绍的PersistentTokenBasedRememberMeService
,是生成token的key,expireTime等信息保存在数据库中的,而TokenBasedRememberService则是将key,expireTime等信息保存在客户端(浏览器)中的,验证方式也所有不同。
看下TokenBasedRememberService中对token的验证实现,大概可以得出这样的结论
5.总结
这篇的内容主要目的是为系统增加‘记住我’功能,相对比较简单,代码的编写量也比较少。在本篇我们也分析了一下Spring Security中实现这个功能的源码,需要实现的主要是PersistentTokenRepository,并在配置中增加RememberMe的配置。
本篇的源代码可在我的github.com中找到,欢迎大家star 精通Spring Boot系列和follow我本人,也可以加我本人微信探讨技术!