Spring Security Remember me使用及原理詳解
Remember me功能就是勾選"記住我"后,一次登錄,后面在有效期內(nèi)免登錄。
先看具體配置:
pom文件:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
Security的配置:
@Autowired private UserDetailsService myUserDetailServiceImpl; // 用戶信息服務(wù) @Autowired private DataSource dataSource; // 數(shù)據(jù)源 @Override protected void configure(HttpSecurity http) throws Exception { // formLogin()是默認(rèn)的登錄表單頁(yè),如果不配置 loginPage(url),則使用 spring security // 默認(rèn)的登錄頁(yè),如果配置了 loginPage()則使用自定義的登錄頁(yè) http.formLogin() // 表單登錄 .loginPage(SecurityConst.AUTH_REQUIRE) .loginProcessingUrl(SecurityConst.AUTH_FORM) // 登錄請(qǐng)求攔截的url,也就是form表單提交時(shí)指定的action .successHandler(loginSuccessHandler) .failureHandler(loginFailureHandler) .and() .rememberMe() .userDetailsService(myUserDetailServiceImpl) // 設(shè)置userDetailsService .tokenRepository(persistentTokenRepository()) // 設(shè)置數(shù)據(jù)訪問(wèn)層 .tokenValiditySeconds(60 * 60) // 記住我的時(shí)間(秒) .and() .authorizeRequests() // 對(duì)請(qǐng)求授權(quán) .antMatchers(SecurityConst.AUTH_REQUIRE, securityProperty.getBrowser().getLoginPage()).permitAll() // 允許所有人訪問(wèn)login.html和自定義的登錄頁(yè) .anyRequest() // 任何請(qǐng)求 .authenticated()// 需要身份認(rèn)證 .and() .csrf().disable() // 關(guān)閉跨站偽造 ; } /** * 持久化token * * Security中,默認(rèn)是使用PersistentTokenRepository的子類InMemoryTokenRepositoryImpl,將token放在內(nèi)存中 * 如果使用JdbcTokenRepositoryImpl,會(huì)創(chuàng)建表persistent_logins,將token持久化到數(shù)據(jù)庫(kù) */ @Bean public PersistentTokenRepository persistentTokenRepository() { JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl(); tokenRepository.setDataSource(dataSource); // 設(shè)置數(shù)據(jù)源 // tokenRepository.setCreateTableOnStartup(true); // 啟動(dòng)創(chuàng)建表,創(chuàng)建成功后注釋掉 return tokenRepository; }
上面的myUserDetailServiceImpl是自己實(shí)現(xiàn)的UserDetailsService接口,dataSource會(huì)自動(dòng)讀取數(shù)據(jù)庫(kù)配置。過(guò)期時(shí)間設(shè)置的3600秒,即一個(gè)小時(shí)
在登錄頁(yè)面加一行(name必須是remeber-me):
"記住我"基本原理:
1、第一次發(fā)送認(rèn)證請(qǐng)求,會(huì)被UsernamePasswordAuthenticationFilter攔截,然后身份認(rèn)證。
認(rèn)證成功后,在AbstracAuthenticationProcessingFilter中,有個(gè)RememberMeServices接口。
該接口默認(rèn)實(shí)現(xiàn)類是NullRememberMeServices,這里會(huì)調(diào)用另一個(gè)實(shí)現(xiàn)抽象類AbstractRememberMeServices
// ... private RememberMeServices rememberMeServices = new NullRememberMeServices(); protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { // ... SecurityContextHolder.getContext().setAuthentication(authResult); // 登錄成功后,調(diào)用RememberMeServices保存Token相關(guān)信息 rememberMeServices.loginSuccess(request, response, authResult); // ... }
2、調(diào)用AbstractRememberMeServices的loginSuccess方法。
可以看到如果request中name為"remember-me"為true時(shí),才會(huì)調(diào)用下面的onLoginSuccess()方法。這也是為什么上面登錄頁(yè)中的表單,name必須是"remember-me"的原因:
3、在Security中配置了rememberMe()之后, 會(huì)由PersistentTokenBasedRememberMeServices去實(shí)現(xiàn)父類AbstractRememberMeServices中的抽象方法。
在PersistentTokenBasedRememberMeServices中,有一個(gè)PersistentTokenRepository,會(huì)生成一個(gè)Token,并將這個(gè)Token寫(xiě)到cookie里面返回瀏覽器。PersistentTokenRepository的默認(rèn)實(shí)現(xiàn)類是InMemoryTokenRepositoryImpl,該默認(rèn)實(shí)現(xiàn)類會(huì)將token保存到內(nèi)存中。這里我們配置了它的另一個(gè)實(shí)現(xiàn)類JdbcTokenRepositoryImpl,該類會(huì)將Token持久化到數(shù)據(jù)庫(kù)中
// ... private PersistentTokenRepository tokenRepository = new InMemoryTokenRepositoryImpl(); protected void onLoginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication) { String username = successfulAuthentication.getName(); logger.debug("Creating new persistent login for user " + username); // 創(chuàng)建一個(gè)PersistentRememberMeToken PersistentRememberMeToken persistentToken = new PersistentRememberMeToken( username, generateSeriesData(), generateTokenData(), new Date()); try { // 保存Token tokenRepository.createNewToken(persistentToken); // 將Token寫(xiě)到Cookie中 addCookie(persistentToken, request, response); } catch (Exception e) { logger.error("Failed to save persistent token ", e); } }
4、JdbcTokenRepositoryImpl將Token持久化到數(shù)據(jù)庫(kù)
/** The default SQL used by <tt>createNewToken</tt> */ public static final String DEF_INSERT_TOKEN_SQL = "insert into persistent_logins (username, series, token, last_used) values(?,?,?,?)"; public void createNewToken(PersistentRememberMeToken token) { getJdbcTemplate().update(insertTokenSql, token.getUsername(), token.getSeries(), token.getTokenValue(), token.getDate()); }
查看數(shù)據(jù)庫(kù),可以看到往persistent_logins 中插入了一條數(shù)據(jù):
5、重啟服務(wù),發(fā)送第二次認(rèn)證請(qǐng)求,只會(huì)攜帶Cookie。
所以直接會(huì)被RememberMeAuthenticationFilter攔截,并且此時(shí)內(nèi)存中沒(méi)有認(rèn)證信息。
可以看到,此時(shí)的RememberMeServices是由PersistentTokenBasedRememberMeServices實(shí)現(xiàn)
6、在PersistentTokenBasedRememberMeServices中,調(diào)用processAutoLoginCookie方法,獲取用戶相關(guān)信息
protected UserDetails processAutoLoginCookie(String[] cookieTokens, HttpServletRequest request, HttpServletResponse response) { if (cookieTokens.length != 2) { throw new InvalidCookieException("Cookie token did not contain " + 2 + " tokens, but contained '" + Arrays.asList(cookieTokens) + "'"); } // 從Cookie中獲取Series和Token final String presentedSeries = cookieTokens[0]; final String presentedToken = cookieTokens[1]; //在數(shù)據(jù)庫(kù)中,通過(guò)Series查詢PersistentRememberMeToken PersistentRememberMeToken token = tokenRepository .getTokenForSeries(presentedSeries); if (token == null) { throw new RememberMeAuthenticationException( "No persistent token found for series id: " + presentedSeries); } // 校驗(yàn)數(shù)據(jù)庫(kù)中Token和Cookie中的Token是否相同 if (!presentedToken.equals(token.getTokenValue())) { tokenRepository.removeUserTokens(token.getUsername()); throw new CookieTheftException( messages.getMessage( "PersistentTokenBasedRememberMeServices.cookieStolen", "Invalid remember-me token (Series/token) mismatch. Implies previous cookie theft attack.")); } // 判斷Token是否超時(shí) if (token.getDate().getTime() + getTokenValiditySeconds() * 1000L < System .currentTimeMillis()) { throw new RememberMeAuthenticationException("Remember-me login has expired"); } if (logger.isDebugEnabled()) { logger.debug("Refreshing persistent login token for user '" + token.getUsername() + "', series '" + token.getSeries() + "'"); } // 創(chuàng)建一個(gè)新的PersistentRememberMeToken PersistentRememberMeToken newToken = new PersistentRememberMeToken( token.getUsername(), token.getSeries(), generateTokenData(), new Date()); try { //更新數(shù)據(jù)庫(kù)中Token tokenRepository.updateToken(newToken.getSeries(), newToken.getTokenValue(), newToken.getDate()); //重新寫(xiě)到Cookie addCookie(newToken, request, response); } catch (Exception e) { logger.error("Failed to update token: ", e); throw new RememberMeAuthenticationException( "Autologin failed due to data access problem"); } //調(diào)用UserDetailsService獲取用戶信息 return getUserDetailsService().loadUserByUsername(token.getUsername()); }
7、獲取用戶相關(guān)信息后,再調(diào)用AuthenticationManager去認(rèn)證授權(quán)
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Spring Security 構(gòu)建rest服務(wù)實(shí)現(xiàn)rememberme 記住我功能
- Spring框架實(shí)現(xiàn)AOP添加日志記錄功能過(guò)程詳解
- Spring Security實(shí)現(xiàn)兩周內(nèi)自動(dòng)登錄"記住我"功能
- SpringBoot Logback日志記錄到數(shù)據(jù)庫(kù)的實(shí)現(xiàn)方法
- Spring Boot假死診斷實(shí)戰(zhàn)記錄
- springboot學(xué)習(xí)筆記之 profile多環(huán)境配置切換的實(shí)現(xiàn)方式
- SpringSecurity rememberme功能實(shí)現(xiàn)過(guò)程解析
相關(guān)文章
springboot+vue制作后臺(tái)管理系統(tǒng)項(xiàng)目
本文詳細(xì)介紹了后臺(tái)管理使用springboot+vue制作,以分步驟、圖文的形式詳細(xì)講解,大家有需要的可以參考參考2021-08-08Spring boot項(xiàng)目整合WebSocket方法
這篇文章主要介紹了WebSocket使用Spring boot整合方法,需要繼承webSocketHandler類,重寫(xiě)幾個(gè)方法就可以了,具體實(shí)例代碼跟隨小編一起看看吧2021-09-09EasyExcel工具讀取Excel空數(shù)據(jù)行問(wèn)題的解決辦法
EasyExcel是阿里巴巴開(kāi)源的一個(gè)excel處理框架,以使用簡(jiǎn)單,節(jié)省內(nèi)存著稱,下面這篇文章主要給大家介紹了關(guān)于EasyExcel工具讀取Excel空數(shù)據(jù)行問(wèn)題的解決辦法,需要的朋友可以參考下2022-08-08Java簡(jiǎn)單實(shí)現(xiàn)猜數(shù)字游戲附C語(yǔ)言版本
猜數(shù)字是興起于英國(guó)的益智類小游戲,起源于20世紀(jì)中期,一般由兩個(gè)人或多人玩,也可以由一個(gè)人和電腦玩。游戲規(guī)則為一方出數(shù)字,一方猜,今天我們來(lái)用Java和C語(yǔ)言分別把這個(gè)小游戲?qū)懗鰜?lái)練練手2021-11-11Java基于Spire Cloud Excel把Excel轉(zhuǎn)換成PDF
這篇文章主要介紹了Java基于Spire Cloud Excel把Excel轉(zhuǎn)換成PDF,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-05-0520秒教你學(xué)會(huì)java?List函數(shù)排序操作示例
這篇文章主要為大家介紹了20秒教你學(xué)會(huì)List函數(shù)排序操作示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09SpringBoot 配置文件加載位置與優(yōu)先級(jí)問(wèn)題詳解
這篇文章主要介紹了SpringBoot 配置文件加載位置與優(yōu)先級(jí)問(wèn)題,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-09-09Java之String字符串在JVM中的存儲(chǔ)及其內(nèi)存地址的問(wèn)題
這篇文章主要介紹了Java之String字符串在JVM中的存儲(chǔ)及其內(nèi)存地址的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-07-07SpringCloud微服務(wù)剔除下線功能實(shí)現(xiàn)原理分析
SpringCloud是一種微服務(wù)的框架,利用它我們可以去做分布式服務(wù)開(kāi)發(fā),這篇文章主要介紹了SpringCloud微服務(wù)剔除下線功能,需要的朋友可以參考下2022-11-11