記住我功能的基本原理
之前有講過,當用戶發起認證請求,會通過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;
}
這裏注入了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);
// 省略其他配置
}
記住我功能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);
}
}
這樣相關的數據庫操作和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);
}
}