一震蒋、前言
如SpringSecurity在用戶名密碼登錄的示例所示:
在UsernamePasswordAuthenticationFilter
的父類Filter中的 doFilter() 方法衷旅,調(diào)用用戶認(rèn)證的方法認(rèn)證用戶成功后供汛,會(huì)調(diào)用一個(gè)名為successfulAuthentication
的方法,它內(nèi)部有幾大過程洛波;
- 1全肮、將認(rèn)證成功的信息存入
SecurityContextHolder
中塞蹭。 - 2、如果
rememberMeServices
功能開啟了门坷,處理rememberMe的邏輯宣鄙。 - 3、調(diào)用
successHandler
成功處理器默蚌。
其中第二點(diǎn)就是今天要說的 "記住我" 的功能冻晤。它的實(shí)現(xiàn)邏輯如下:
二、流程梳理
1敏簿、第一次賦值流程(假設(shè)已經(jīng)開啟了rememberMe認(rèn)證流程)
在PersistentTokenBasedRememberMeServices
類中明也,先是生成一個(gè)PersistentRememberMeToken
類型的 token宣虾,并通過tokenReposority.createNewToken(token)
方法存儲(chǔ)這個(gè)token,最后將token信息存入cookie中返回前端温数。
這里的
tokenReposority
是需要我們自己配置的绣硝,SpringSecurity提前提供好了兩個(gè)可供使用的類
InMemoryTokenRepositoryImpl
顧名思義,將token存儲(chǔ)在內(nèi)存中撑刺,特點(diǎn)是快鹉胖,但是消耗內(nèi)存,用戶量少的話可以使用够傍。內(nèi)部原理也很簡(jiǎn)單甫菠,就是將不同的token存儲(chǔ)在一個(gè)HashMap里面。
@Bean
public PersistentTokenRepository persistentTokenRepository(){
InMemoryTokenRepositoryImpl tokenRepository = new InMemoryTokenRepositoryImpl ();
return tokenRepository;
}
和
JdbcTokenRepositoryImpl
這個(gè)是將token存儲(chǔ)在數(shù)據(jù)庫中的選擇冕屯,下面setCreateTableOnStartup選中的ture會(huì)自動(dòng)在數(shù)據(jù)庫中創(chuàng)建一個(gè)表來存儲(chǔ)數(shù)據(jù)(第二次啟動(dòng)項(xiàng)目記得改為false寂诱,因?yàn)楸淼谝淮螁?dòng)已經(jīng)創(chuàng)建了,第二次還是true會(huì)報(bào)錯(cuò))
@Bean
public PersistentTokenRepository persistentTokenRepository(){
JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
tokenRepository.setCreateTableOnStartup(true);
tokenRepository.setDataSource(dataSource);
return tokenRepository;
}
其實(shí)安聘,我們還可以自定義這個(gè)TokenRepository痰洒,只需要去實(shí)現(xiàn)上述說的兩個(gè)類的接口PersistentTokenRepository
即可。
public interface PersistentTokenRepository {
void createNewToken(PersistentRememberMeToken token);
void updateToken(String series, String tokenValue, Date lastUsed);
PersistentRememberMeToken getTokenForSeries(String seriesId);
void removeUserTokens(String username);
}
2浴韭、過濾流程
第一次我登錄了之后丘喻,因?yàn)闀?huì)話的關(guān)系,我們可以訪問一些資源念颈。但是當(dāng)我關(guān)閉頁面泉粉,在會(huì)話消失后,我們的訪問一個(gè)后臺(tái)資源的話榴芳,按照以往的邏輯嗡靡,應(yīng)該是訪問不到且會(huì)跳轉(zhuǎn)到登錄頁面。但是如果我們之前的會(huì)話有RememberMe的話翠语,cookie中帶有一個(gè)名為remember-me
的信息叽躯,在通過前面幾個(gè)過濾器之后,到了名為RememberMeAuthenticationFilter
的過濾器中的時(shí)候肌括,它會(huì)從request中拿取cookie信息点骑,并嘗試通過token去獲取用戶信息,成功獲取到之后會(huì)通過UserDetailService認(rèn)證谍夭,認(rèn)證通過即可放行黑滴。
上面的aotuLogin()方法如下
最后,附上開啟記住我功能的配置
// tokenRepository配置
@Bean
public PersistentTokenRepository persistentTokenRepository(){
JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
tokenRepository.setCreateTableOnStartup(false);
tokenRepository.setDataSource(dataSource);
return tokenRepository;
}
// 默認(rèn)的密碼加密
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
// 這個(gè)自定義即可
@Bean
public UserDetailsService demoUser() {
return (username) -> new User(username, passwordEncoder().encode("123456"),
AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_USER"));
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
.loginProcessingUrl("/testToLogin")
.loginPage("/needLogin")
.successHandler((request, response, authentication) -> {
PrintWriter writer = response.getWriter();
writer.print("強(qiáng)啊紧索,成了得嘛");
}).failureHandler((request, response, exception) -> {
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(om.writeValueAsString("login failure"));
})
.and()
.rememberMe() // -----------就是這里了
.alwaysRemember(true) // 最近發(fā)現(xiàn)新版本要多配置一個(gè)這個(gè)袁辈,否則也沒有開啟
.tokenValiditySeconds(3600)
.tokenRepository(persistentTokenRepository())
.userDetailsService(demoUser())
.and()
.authorizeRequests()
.antMatchers("/skip", "/needLogin").permitAll()
.anyRequest()
.authenticated()
.and()
.csrf().disable();
}