springsecurity记住我RememberMe功能
# 记住我功能的基本原理
之前有讲过,当用户发起认证请求,会通过UsernamePasswordAuthenticationFilter,在认证成功之后,可以调用SpringSecurity提供的RememberMeService,它会生成一个Token并将它写入浏览器的Cookie中,同时这个它里面有一个TokenRepository
,TokenRepository会将Token放入数据库中。
当下次浏览器再请求的时候,会经过RememberMeAuthenticationFiler
,在这个filter里面会读取Cookie中的Token,然后去数据库中查找是否有相应的Token,然后再通过UserDetailsService获取用户的信息。
# 记住我功能具体实现
我们就按照它的基本原理来挨着顺序做相应的配置就可以了
@Autowired
private DataSource dataSource;
@Autowired
private UserDetailsService myUserDetailsService;
/**
* 配置TokenRepository
* @return
*/
@Bean
public PersistentTokenRepository persistentTokenRepository() {
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
// 配置数据源
jdbcTokenRepository.setDataSource(dataSource);
// 第一次启动的时候自动建表(可以不用这句话,自己手动建表,源码中有语句的)
// jdbcTokenRepository.setCreateTableOnStartup(true);
return jdbcTokenRepository;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
这里注入了UserDetailsService,同时配置了一下TokenRepository,因为需要和数据库进行交互,所以DataSource是必须的,这里就不贴出配置相关的代码了。
接下来就是在config方法中做相应的配置了
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
.rememberMe() // 记住我相关配置
.tokenRepository(persistentTokenRepository()) // 设置TokenRepository
// 配置Cookie过期时间
.tokenValiditySeconds(securityProperties.getBrowser().getRememberMeSeconds())
// 配置UserDetailsService
.userDetailsService(myUserDetailsService);
// 省略其他配置
}
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# 记住我功能SpringSecurity源码分析
根据最开始的原理分析,我们可以知道一般是在认证成功之后,才会去做记住我的操作,所以我们可以看successfulAuthentication方法中的相关代码
// AbstractAuthenticationProcessingFilter.java
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
SecurityContextHolder.getContext().setAuthentication(authResult);
// 调用rememberMeServices,进行登录操作
this.rememberMeServices.loginSuccess(request, response, authResult);
if (this.eventPublisher != null) {
this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
}
this.successHandler.onAuthenticationSuccess(request, response, authResult);
}
这里在认证成功之后,调用了RememberMeServices的loginSuccess方法,该方法会进一步调用onLoginSuccess:
protected void onLoginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication) {
String username= successfulAuthentication.getName();
PersistentRememberMeToken persistentToken = new PersistentRememberMeToken(username, this.generateSeriesData(), this.generateTokenData(), new Date());
try {
// 进行TokenRepository操作
this.tokenRepository.createNewToken(persistentToken);
// 进行Cookie操作
this.addCookie(persistentToken, request, response);
} catch (Exception var7) {
this.logger.error("Failed to save persistent token ", var7);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
这样相关的数据库操作和Token操作就完成了。
接下来看看下次登录的时候,是怎么进行记住我认证的。这里我们就直接看RememberMeAuthenticationFilter
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)res;
// 1.先判断之前是否已经通过认证了
if (SecurityContextHolder.getContext().getAuthentication() == null) {
// 2.通过RememberMeServices进行登录
Authentication rememberMeAuth = this.rememberMeServices.autoLogin(request, response);
if (rememberMeAuth != null) {
try {
rememberMeAuth = this.authenticationManager.authenticate(rememberMeAuth);
SecurityContextHolder.getContext().setAuthentication(rememberMeAuth);
this.onSuccessfulAuthentication(request, response, rememberMeAuth);
if (this.logger.isDebugEnabled()) {
this.logger.debug("SecurityContextHolder populated with remember-me token: '" + SecurityContextHolder.getContext().getAuthentication() + "'");
}
if (this.eventPublisher != null) {
this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(SecurityContextHolder.getContext().getAuthentication(), this.getClass()));
}
if (this.successHandler != null) {
this.successHandler.onAuthenticationSuccess(request, response, rememberMeAuth);
return;
}
} catch (AuthenticationException var8) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("SecurityContextHolder not populated with remember-me token, as AuthenticationManager rejected Authentication returned by RememberMeServices: '" + rememberMeAuth + "'; invalidating remember-me token", var8);
}
this.rememberMeServices.loginFail(request, response);
this.onUnsuccessfulAuthentication(request, response, var8);
}
}
chain.doFilter(request, response);
} else {
if (this.logger.isDebugEnabled()) {
this.logger.debug("SecurityContextHolder not populated with remember-me token, as it already contained: '" + SecurityContextHolder.getContext().getAuthentication() + "'");
}
chain.doFilter(request, response);
}
}
# 测试
关掉并重启应用后,访问的时候,没有跳到登陆页。
参考文章: