最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Spring Security 實現(xiàn)“記住我”功能及原理解析

 更新時間:2020年05月20日 14:44:06   作者:木兮同學  
這篇文章主要介紹了Spring Security 實現(xiàn)“記住我”功能及原理解析,需要的朋友可以參考下

這章繼續(xù)擴展功能,來一個“記住我”的功能實現(xiàn),就是說用戶在登錄一次以后,系統(tǒng)會記住這個用戶一段時間,這段時間內(nèi)用戶不需要重新登錄就可以使用系統(tǒng)。

記住我功能基本原理

原理說明

  • 用戶登錄發(fā)送認證請求的時候會被UsernamePasswordAuthenticationFilter認證攔截,認證成功以后會調(diào)用一個RememberMeService服務(wù),服務(wù)里面有一個TokenRepository,這個服務(wù)會生成一個Token,然后將Token寫入到瀏覽器的Cookie同時會使用TokenRepository把生成的Token寫到數(shù)據(jù)庫里面,因為這個動作是在認證成功以后做的,所以在Token寫入數(shù)據(jù)庫的時候會把用戶名同時寫入數(shù)據(jù)庫。
  • 假如瀏覽器關(guān)了重新訪問系統(tǒng),用戶不需要再次登錄就可以訪問,這個時候請求在過濾器鏈上會經(jīng)過RememberMeAuthenticationFilter,這個過濾器的作用是讀取Cookie中的Token交給RemeberMeService,RemeberMeService會用TokenRepository到數(shù)據(jù)庫里去查這個Token在數(shù)據(jù)庫里有沒有記錄,如果有記錄就會把用戶名取出來,取出來以后會進行各種校驗然后生成新Token再調(diào)用之前的UserDetailService,去獲取用戶的信息,然后把用戶信息放到SecurityContext里面,到這里就把用戶給登錄上了。

圖解說明

流程圖解

RememberMeAuthenticationFilter位于過濾器鏈的哪一環(huán)?

圖解

在這里插入圖片描述

首先其他認證過濾器會先進行認證,當其他過濾器都無法認證時,RememberMeAuthenticationFilter會嘗試去做認證。

記住我功能具體實現(xiàn)

前端頁面

登錄的時候加上一行記住我的勾選按鈕,這里要注意,name一定要是remember-me,下面源碼部分會提到。

<tr>
				<td colspan='2'><input name="remember-me" type="checkbox" value="true" />記住我</td>
			</tr>

后臺

首先配置TokenRepositoryBean

/**
	 * 記住我功能的Token存取器配置
	 * 
	 * @return
	 */
	@Bean
	public PersistentTokenRepository persistentTokenRepository() {
		JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
		tokenRepository.setDataSource(dataSource);
		// 啟動的時候自動創(chuàng)建表,建表語句 JdbcTokenRepositoryImpl 已經(jīng)都寫好了
		tokenRepository.setCreateTableOnStartup(true);
		return tokenRepository;
	}

然后需要在 configure 配置方法那邊進行記住我功能所有組件的配置

protected void configure(HttpSecurity http) throws Exception {
		ValidateCodeFilter validateCodeFilter = new ValidateCodeFilter();
		http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)
				.formLogin()
				.loginPage("/authentication/require")
				.loginProcessingUrl("/authentication/form")
				.successHandler(meicloudAuthenticationSuccessHandler)
				.failureHandler(meicloudAuthenticationFailureHandler)
				// 配置記住我功能
				.and()
				.rememberMe()
				// 配置TokenRepository
				.tokenRepository(persistentTokenRepository())
				// 配置Token過期時間
				.tokenValiditySeconds(3600)
				// 最終拿到用戶名之后,使用UserDetailsService去做登錄
				.userDetailsService(userDetailsService)
				.and()
				.authorizeRequests()
				.antMatchers("/authentication/require", securityProperties.getBrowser().getSignInPage(), "/code/image").permitAll()
				.anyRequest()
				.authenticated()
				.and()
				.csrf().disable();

	}

記住我功能Spring Security源碼解析

登錄之前“記住我”源碼流程

在認證成功之后,會調(diào)用successfulAuthentication方法(這些第五章源碼部分已經(jīng)學習過),在將認證信息保存到Context后,RememberMeServices就會調(diào)用它的loginSuccess方法。

 protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
 if (this.logger.isDebugEnabled()) {
  this.logger.debug("Authentication success. Updating SecurityContextHolder to contain: " + authResult);
 }

 SecurityContextHolder.getContext().setAuthentication(authResult);
 this.rememberMeServices.loginSuccess(request, response, authResult);
 if (this.eventPublisher != null) {
  this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
 }

 this.successHandler.onAuthenticationSuccess(request, response, authResult);
 }

loginSuccess方法里面會先檢查請求中是否有name為remember-me的參數(shù),有才進行下一步。

 public final void loginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication) {
 	// this.parameter = "remember-me"
 if (!this.rememberMeRequested(request, this.parameter)) {
  this.logger.debug("Remember-me login not requested.");
 } else {
  this.onLoginSuccess(request, response, successfulAuthentication);
 }
 }

再進入onLoginSuccess方法,里面主要就是進行寫庫和寫Cookie的操作。

 protected void onLoginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication) {
 String username = successfulAuthentication.getName();
 this.logger.debug("Creating new persistent login for user " + username);
 // 生成Token
 PersistentRememberMeToken persistentToken = new PersistentRememberMeToken(username, this.generateSeriesData(), this.generateTokenData(), new Date());
 try {
 	// 將Token和userName插入數(shù)據(jù)庫
  this.tokenRepository.createNewToken(persistentToken);
  // 將Token寫到Cookie中
  this.addCookie(persistentToken, request, response);
 } catch (Exception var7) {
  this.logger.error("Failed to save persistent token ", var7);
 }
 }

登錄之后“記住我”源碼流程

首先會進入RememberMeAuthenticationFilter,會先判斷前面的過濾器是否進行過認證(Context中是否有認證信息),未進行過認證的話會調(diào)用RememberMeServices的autoLogin方法。

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
 HttpServletRequest request = (HttpServletRequest)req;
 HttpServletResponse response = (HttpServletResponse)res;
 if (SecurityContextHolder.getContext().getAuthentication() == null) {
  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);
 }
 }

autoLogin方法里面,主要調(diào)用this.processAutoLoginCookie(cookieTokens, request, response)這個方法獲取數(shù)據(jù)庫中的用戶信息,其步驟是:

  • 解析前端傳來的Cookie,里面包含了Token和seriesId,它會使用seriesId查找數(shù)據(jù)庫的Token
  • 檢查Cookie中的Token和數(shù)據(jù)庫查出來的Token是否一樣
  • 一樣的話再檢查數(shù)據(jù)庫中的Token是否已過期
  • 如果以上都符合的話,會使用舊的用戶名和series重新new一個Token,這時過期時間也重新刷新
  • 然后將新的Token保存回數(shù)據(jù)庫,同時添加回Cookie
  • 最后再調(diào)用UserDetailsService的loadUserByUsername方法返回UserDetails
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) + "'");
 } else {
  String presentedSeries = cookieTokens[0];
  String presentedToken = cookieTokens[1];
  PersistentRememberMeToken token = this.tokenRepository.getTokenForSeries(presentedSeries);
  if (token == null) {
  throw new RememberMeAuthenticationException("No persistent token found for series id: " + presentedSeries);
  } else if (!presentedToken.equals(token.getTokenValue())) {
  this.tokenRepository.removeUserTokens(token.getUsername());
  throw new CookieTheftException(this.messages.getMessage("PersistentTokenBasedRememberMeServices.cookieStolen", "Invalid remember-me token (Series/token) mismatch. Implies previous cookie theft attack."));
  } else if (token.getDate().getTime() + (long)this.getTokenValiditySeconds() * 1000L < System.currentTimeMillis()) {
  throw new RememberMeAuthenticationException("Remember-me login has expired");
  } else {
  if (this.logger.isDebugEnabled()) {
   this.logger.debug("Refreshing persistent login token for user '" + token.getUsername() + "', series '" + token.getSeries() + "'");
  }
  PersistentRememberMeToken newToken = new PersistentRememberMeToken(token.getUsername(), token.getSeries(), this.generateTokenData(), new Date());
  try {
   this.tokenRepository.updateToken(newToken.getSeries(), newToken.getTokenValue(), newToken.getDate());
   this.addCookie(newToken, request, response);
  } catch (Exception var9) {
   this.logger.error("Failed to update token: ", var9);
   throw new RememberMeAuthenticationException("Autologin failed due to data access problem");
  }

  return this.getUserDetailsService().loadUserByUsername(token.getUsername());
  }
 }
 }

回到RememberMeAuthenticationFilter,在調(diào)用了autoLogin方法之后得到了rememberMeAuth,然后再對其進行一個認證,認證成功之后保存到SecurityContext中,至此整個RememberMe自動登錄流程源碼結(jié)束。

相關(guān)閱讀:

Spring Security實現(xiàn)圖形驗證碼登錄

Spring Security實現(xiàn)短信驗證碼登錄

總結(jié)

到此這篇關(guān)于Spring Security 實現(xiàn)“記住我”功能及原理解析的文章就介紹到這了,更多相關(guān)spring security記住我內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java異常javax.net.ssl.SSLHandshakeException: SSL的解決方法

    Java異常javax.net.ssl.SSLHandshakeException: SSL的解決方法

    在Java開發(fā)過程中,SSL(Secure Sockets Layer)握手異常是一個常見的網(wǎng)絡(luò)通信錯誤,特別是在使用HTTPS協(xié)議進行安全通信時,本文將詳細分析javax.net.ssl.SSLHandshakeException: SSL這一異常的背景、可能的原因,并通過代碼示例幫助您理解和解決這一問題
    2024-12-12
  • SpringBoot 集成 Jasypt 對數(shù)據(jù)庫加密以及踩坑的記錄分享

    SpringBoot 集成 Jasypt 對數(shù)據(jù)庫加密以及踩坑的記錄分享

    這篇文章主要介紹了SpringBoot 集成 Jasypt 對數(shù)據(jù)庫加密以及踩坑,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-08-08
  • 詳解Javascript判斷Crontab表達式是否合法

    詳解Javascript判斷Crontab表達式是否合法

    這篇文章主要介紹了詳解Javascript判斷Crontab表達式是否合法的相關(guān)資料,需要的朋友可以參考下
    2017-03-03
  • SpringBoot源碼分析之bootstrap.properties文件加載的原理

    SpringBoot源碼分析之bootstrap.properties文件加載的原理

    本文通過訪問看到bootstrap.properties中的信息獲取到了,同時age也被application.properties中的屬性覆蓋掉了。加載順序到底是什么?為什么會覆蓋呢?我們接下來分析下吧
    2021-12-12
  • Spring MVC 自定義數(shù)據(jù)轉(zhuǎn)換器的思路案例詳解

    Spring MVC 自定義數(shù)據(jù)轉(zhuǎn)換器的思路案例詳解

    本文通過兩個案例來介紹下Spring MVC 自定義數(shù)據(jù)轉(zhuǎn)換器的相關(guān)知識,每種方法通過實例圖文相結(jié)合給大家介紹的非常詳細,需要的朋友可以參考下
    2021-09-09
  • Java實現(xiàn)Token登錄驗證的項目實踐

    Java實現(xiàn)Token登錄驗證的項目實踐

    本文主要介紹了Java實現(xiàn)Token登錄驗證的項目實踐,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2023-03-03
  • Java多線程之同步工具類CyclicBarrier

    Java多線程之同步工具類CyclicBarrier

    這篇文章主要介紹Java多線程之同步工具類CyclicBarrier,它是一個同步工具類,它允許一組線程互相等待,直到達到某個公共屏障點,支持一個可選的Runnable命令,在一組線程中的最后一個線程到達之后,該命令只在每個屏障點運行一次。下面來看文章具體內(nèi)容
    2021-10-10
  • 詳解Mybatis逆向工程中使用Mysql8.0版本驅(qū)動遇到的問題

    詳解Mybatis逆向工程中使用Mysql8.0版本驅(qū)動遇到的問題

    今天在使用 8.0.12 版的 mysql 驅(qū)動時遇到了各種各樣的坑。這篇文章主要介紹了詳解Mybatis逆向工程中使用Mysql8.0版本驅(qū)動遇到的問題,感興趣的小伙伴們可以參考一下
    2018-10-10
  • SpringBoot項目整合Redis教程詳解

    SpringBoot項目整合Redis教程詳解

    這篇文章主要介紹了SpringBoot項目整合Redis教程詳解,Redis?是完全開源的,遵守?BSD?協(xié)議,是一個高性能的?key-value?數(shù)據(jù)庫。感興趣的小伙伴可以參考閱讀本文
    2023-03-03
  • Java9以后的垃圾回收的具體用法

    Java9以后的垃圾回收的具體用法

    這篇文章主要介紹了Java9以后的垃圾回收的具體用法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2019-10-10

最新評論

漳浦县| 商都县| 商水县| 松江区| 新疆| 扶余县| 双牌县| 宁强县| 台东市| 乌拉特中旗| 林甸县| 荆州市| 简阳市| 乌审旗| 鄂托克前旗| 深州市| 芮城县| 景泰县| 华坪县| 栖霞市| 望江县| 门源| 洛浦县| 枞阳县| 论坛| 临桂县| 兰溪市| 巴林右旗| 米脂县| 安丘市| 宜兰市| 屏南县| 麻阳| 闽侯县| 凯里市| 河津市| 冷水江市| 吴桥县| 河南省| 延安市| 通化县|