一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Java教程 - Spring Security基于散列加密方案實現自動登錄功能

Spring Security基于散列加密方案實現自動登錄功能

2022-01-19 01:05一一哥Sun Java教程

為了提高項目的用戶體驗,我們可以在項目中添加自動登錄功能,當然也要給用戶提供退出登錄的功能。接下來學習下Spring Security基于散列加密方案實現自動登錄功能,一起看看吧

前言

在前面的2個章節中,一一哥 帶大家實現了在Spring Security中添加圖形驗證碼校驗功能,其實Spring Security的功能不僅僅是這些,還可以實現很多別的效果,比如實現自動登錄,注銷登錄等

有的小伙伴會問,我們為什么要實現自動登錄啊?這個需求其實還是很常見的,因為對于用戶來說,他可能經常需要進行登錄以及退出登錄,你想想,如果用戶每次登錄時都要輸入自己的用戶名和密碼,是不是很煩,用戶體驗是不是很不好?

所以為了提高項目的用戶體驗,我們可以在項目中添加自動登錄功能,當然也要給用戶提供退出登錄的功能。接下來就跟著 一一哥 來學習如何實現這些功能吧!

 

一. 自動登錄簡介

1. 為什么要自動登錄

我們在訪問網站或app時,一般都會要求我們注冊一個賬號,包含用戶名和密碼信息,其中密碼還會有長度及取值范圍的限制。很多時候,我們在不同的網站上注冊的賬號,可能密碼也不同,這就導致我們必須記住這些不同網站上的用戶信息。那么在下次登錄時,因為我們的密碼太多了,很有可能會記不起這些賬號密碼。所以在幾次嘗試登錄失敗之后,很多人都會選擇找回密碼,從而再次陷入如何設置密碼的循環里。

為了盡可能減少用戶重新登錄的頻率,提高用戶的使用體驗,我們可以提供自動登錄這樣一個會給用戶帶來便利,同時也會給用戶帶來風險的體驗性功能。

2. 自動登錄的實現方案

了解了自動登錄出現的背景及作用后,那么我們該怎么實現自動登錄呢?

首先我們知道,自動登錄是將用戶的登錄信息保存在用戶瀏覽器的cookie中,當用戶下次訪問時,自動實現校驗并建立登錄狀態的一種機制。

所以基于上面的原理,Spring Security 就為我們提供了兩種比較好的實現自動登錄的方案:

  • 基于散列加密算法機制:加密用戶必要的登錄信息,并生成令牌來實現自動登錄,利用TokenBasedRememberMeServices類來實現。
  • 基于數據庫等持久化數據存儲機制:生成持久化令牌來實現自動登錄,利用PersistentTokenBasedRememberMeServices來實現。

我上面提到的2個實現類,其實都是AbstractRememberMeServices的子類,如下圖所示:

Spring Security基于散列加密方案實現自動登錄功能

Spring Security基于散列加密方案實現自動登錄功能

了解了這些核心API之后,我們就可以利用這兩個API來實現自動登錄了。

 

二. 基于散列加密方案實現自動登錄

我先帶各位利用第1種實現方案,即基于散列加密方案來實現自動登錄。

首先我們還是在之前的案例基礎之上進行開發,具體的項目創建過程略過,請參考之前的章節內容。

1. 配置加密令牌的key

首先我們創建一個application.yml文件,在其中添加數據庫配置,以及一個用來加密令牌的key字符串,字符串的值隨便自定義就行

spring:
datasource:
  url: jdbc:mysql://localhost:3306/db-security?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT
  username: root
  password: syc
security:
  remember-me:
    key: yyg

2. 配置SecurityConfig類

跟之前的案例一樣,我還是要創建一個SecurityConfig類,在其中的configure(HttpSecurity http)方法中,通過JdbcTokenRepositoryImpl關聯我們的數據庫,并且通過rememberMe()方法開啟“記住我”功能,另外還要把我們前面在配置文件中的rememberKey配置進來,作為散列加密的key

@EnableWebSecurity(debug = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

  @Value("${spring.security.remember-me.key}")
  private String rememberKey;

  @Autowired
  private UserDetailsService userDetailsService;

  @Override
  protected void configure(HttpSecurity http) throws Exception {
      //利用JdbcTokenRepositoryImpl關聯數據源
      JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
      tokenRepository.setDataSource(dataSource);

      http.authorizeRequests()
              .antMatchers("/admin/**")
              .hasRole("ADMIN")
              .antMatchers("/user/**")
              .hasRole("USER")
              .antMatchers("/app/**")
              .permitAll()
              .anyRequest()
              .authenticated()
              .and()
              .formLogin()
              .permitAll()
              .and()
          	//開啟“記住我”功能
              .rememberMe()
              .userDetailsService(userDetailsService)
              //配置散列加密用的key
              .key(rememberKey)
              .and()
              .csrf()
              .disable();
  }

  @Bean
  public PasswordEncoder passwordEncoder() {
      //不對登錄密碼進行加密
      return NoOpPasswordEncoder.getInstance();
  }
  
}

3. 添加測試接口

為了方便后續的測試,我隨便編寫一個測試用的web接口。

@RestController
@RequestMapping("/user")
public class UserController {

  @GetMapping("hello")
  public String hello() {

      return "hello, user";
  }

}

4. 啟動項目測試

然后我們把項目啟動起來進行測試,當然你別忘了編寫項目入口類,這里我就不粘貼相關代碼了。

我們訪問一下/user/hello接口,會先重定向到/login接口,這時候會發現在默認的登錄頁面上多了一個“記住我”功能。

Spring Security基于散列加密方案實現自動登錄功能

此時如果我們打開 開發者調試工具,并且勾選“記住我”,然后發起請求,這時候我們會在控制臺看到remember-me的cookie信息,說明Spring Security已經自動生成了remember-me這個cookie,且表單中的remember-me參數也處于了“on”狀態。

Spring Security基于散列加密方案實現自動登錄功能

Spring Security基于散列加密方案實現自動登錄功能

也就是說,我們利用簡單的幾行代碼,就實現了基于散列加密方案的自動登錄。

 

三. 散列加密方案實現原理

你可能會很好奇,散列加密方案到底是怎么實現自動登錄的呢?別急,接下來 壹哥就為你分析一下散列加密的實現原理。

1. cookie的加密原理分析

我在前面給各位說過,自動登錄其實就是將用戶的登錄信息保存在用戶瀏覽器的cookie中,當用戶下次訪問時,自動實現校驗并建立登錄狀態的一種機制。所以在自動登錄后,肯定會生成代表用戶的cookie信息,但是為了安全,這個cookie肯定不會明文存儲,需要把這個cookie進行加密處理,當然也會解碼處理。所以接下來我就給各位分析一下這個cookie的加密和解碼過程。

首先 壹哥 給各位解釋一下所謂的散列加密算法,其實質就是把 username、expirationTime、password等字段,再加上自定義的key字段合并起來,在每個字段之間用 ":" 分隔,最后利用md5算法進行哈希運算這樣就可以得到一個加密后的字符串。Spring Security把這個加密的字符串存儲到cookie中,作為用戶已登錄的標識信息。

然后 壹哥 帶你看看TokenBasedRememberMeServices源碼類中的makeTokenSignature()方法,你會看到散列加密算法的具體加密實現過程,源碼如下圖所示:

Spring Security基于散列加密方案實現自動登錄功能

2. cookie的解碼原理分析

上面利用MD5進行了加密,用戶在下次登錄后,肯定需要進行信息的比對,以判斷用戶信息是否一致。Spring Security是先對cookie中的信息進行解碼,然后與之前記錄的登錄信息進行比對,以此判斷用戶是否已登錄。

Spring Security是在AbstractRememberMeServices類的decodeCookie()方法中,利用Base64對cookie進行解碼,如下圖所示:

Spring Security基于散列加密方案實現自動登錄功能

對于以上2個源碼方法,我們可以簡化抽取出如下兩行代碼:

//對各字段進行散列加密
hashInfo=md5Hex(username +":"+expirationTime +":"password+":"+key)

//利用base64進行解碼
rememberCookie=base64(username+":"+expirationrime+":"+hashInfo)

其中,expirationTime是指本次自動登錄的有效期,key是自己指定的一個散列鹽值,用于防止令牌被修改。利用以上兩個

分析完源碼之后,壹哥給各位簡單總結一下cookie的生成驗證原理:

  • 首先利用上面的源碼生成cookie,并保存在瀏覽器中;
  • 在瀏覽器關閉并重新打開之后,用戶再去訪問 /user/hello 接口時,此時就會攜帶remember-me這個cookie到服務端;
  • 服務器端拿到cookie之后,利用Base64進行解碼,計算出用戶名和過期時間,再根據用戶名查詢到用戶密碼;
  • 最后還要通過 MD5 散列函數計算出散列值,并將計算出的散列值和瀏覽器傳遞來的散列值進行對比,以此確認這個令牌是否有效。

3. 自動登錄的源碼分析

上面分析完cookie信息的加密和解碼之后,接下來我再結合源碼,從兩個方面來介紹自動登錄的實現過程,一個是 remember-me 令牌的生成的過程,另一個則是該令牌的解析過程

3.1 令牌生成的源碼分析

我們要想知道源碼中是如何生成remember-me自動登錄令牌的,首先得知道Spring Security是如何進入到該令牌所在代碼的,這個代碼的執行與我們前一章節所講的Spring Security的認證授權有關,請進入到前面查看。

AbstractAuthenticationProcessingFilter#doFilter ->
AbstractAuthenticationProcessingFilter#successfulAuthentication ->
AbstractRememberMeServices#loginSuccess ->
TokenBasedRememberMeServices#onLoginSuccess

這個令牌生成的核心處理方法定義在:TokenBasedRememberMeServices#onLoginSuccess。

@Override
public void onLoginSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication successfulAuthentication) {
  //從認證對象中獲取用戶名
	String username = retrieveUserName(successfulAuthentication);
  //從認證對象中獲取密碼
	String password = retrievePassword(successfulAuthentication);
  
  ......
  
	if (!StringUtils.hasLength(password)) {
      //根據用戶名查詢出對應的用戶
	 	UserDetails user = getUserDetailsService().loadUserByUsername(username);
      //獲取到用戶身上的密碼
		password = user.getPassword();
	}
  
  //獲取登錄過期時間,默認是2周
	int tokenLifetime = calculateLoginLifetime(request, successfulAuthentication);
	long expiryTime = System.currentTimeMillis();
	expiryTime += 1000L * (tokenLifetime < 0 ? TWO_WEEKS_S : tokenLifetime);
  
  //生成remember-me簽名信息
	String signatureValue = makeTokenSignature(expiryTime, username, password);
  
  //保存cookie
	setCookie(new String[] { username, Long.toString(expiryTime), signatureValue },
  tokenLifetime, request, response);
}

protected String makeTokenSignature(long tokenExpiryTime, String username,
String password) {
	String data = username + ":" + tokenExpiryTime + ":" + password + ":" + getKey();
	MessageDigest digest;
	digest = MessageDigest.getInstance("MD5");
	return new String(Hex.encode(digest.digest(data.getBytes())));
}

以上源碼的實現邏輯很好理解:

  1. 首先從登錄成功的 Authentication 對象中提取出用戶名/密碼;
  2. 由于登錄成功之后,密碼可能被擦除了,所以如果一開始沒有拿到密碼,就再從 UserDetailsService 中重新加載用戶并重新獲取密碼;
  3. 接下來獲取令牌的有效期,令牌有效期默認是兩周;
  4. 再接下來調用 makeTokenSignature()方法 去計算散列值,實際上就是根據 username、令牌有效期以及 password、key 一起計算一個散列值。如果我們沒有自己去設置這個 key,默認是在 RememberMeConfigurer#getKey 方法中進行設置的,它的值是一個 UUID 字符串。但是如果服務端重啟,這個默認的 key 是會變的,這樣就導致之前派發出去的所有 remember-me 自動登錄令牌失效,所以我們可以指定這個 key。
  5. 最后,將用戶名、令牌有效期以及計算得到的散列值放入 Cookie 中并隨response返回。

3.2 令牌解析的源碼分析

對于RememberMe 這個功能,Spring Security提供了 RememberMeAuthenticationFilter 這個過濾器類來處理相關功能,我們來看下 RememberMeAuthenticationFilter 的 doFilter() 方法:

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 = rememberMeServices.autoLogin(request,
					response);

			if (rememberMeAuth != null) {
				// Attempt authenticaton via AuthenticationManager
				try {
					rememberMeAuth = authenticationManager.authenticate(rememberMeAuth);

					// Store to SecurityContextHolder
					SecurityContextHolder.getContext().setAuthentication(rememberMeAuth);

					onSuccessfulAuthentication(request, response, rememberMeAuth);

					if (logger.isDebugEnabled()) {
						logger.debug("SecurityContextHolder populated with remember-me token: '"
								+ SecurityContextHolder.getContext().getAuthentication()
								+ "'");
					}

					// Fire event
					if (this.eventPublisher != null) {
						eventPublisher
								.publishEvent(new InteractiveAuthenticationSuccessEvent(
										SecurityContextHolder.getContext()
												.getAuthentication(), this.getClass()));
					}

					if (successHandler != null) {
						successHandler.onAuthenticationSuccess(request, response,
								rememberMeAuth);

						return;
					}

				}
				catch (AuthenticationException authenticationException) {
					if (logger.isDebugEnabled()) {
						logger.debug(
								"SecurityContextHolder not populated with remember-me token, as "
										+ "AuthenticationManager rejected Authentication returned by RememberMeServices: '"
										+ rememberMeAuth
										+ "'; invalidating remember-me token",
								authenticationException);
					}

					rememberMeServices.loginFail(request, response);

					onUnsuccessfulAuthentication(request, response,
							authenticationException);
				}
			}

			chain.doFilter(request, response);
		}
		else {
			if (logger.isDebugEnabled()) {
				logger.debug("SecurityContextHolder not populated with remember-me token, as it already contained: '"
						+ SecurityContextHolder.getContext().getAuthentication() + "'");
			}

			chain.doFilter(request, response);
		}
	}

這個方法最關鍵的地方在于,如果從 SecurityContextHolder 中無法獲取到當前登錄用戶實例,那么就調用 rememberMeServices.autoLogin()邏輯進行登錄,我們來看下這個方法:

  @Override
	public final Authentication autoLogin(HttpServletRequest request,
			HttpServletResponse response) {
		String rememberMeCookie = extractRememberMeCookie(request);

		if (rememberMeCookie == null) {
			return null;
		}

		logger.debug("Remember-me cookie detected");

		if (rememberMeCookie.length() == 0) {
			logger.debug("Cookie was empty");
			cancelCookie(request, response);
			return null;
		}

		UserDetails user = null;

		try {
			String[] cookieTokens = decodeCookie(rememberMeCookie);
			user = processAutoLoginCookie(cookieTokens, request, response);
			userDetailsChecker.check(user);

			logger.debug("Remember-me cookie accepted");

			return createSuccessfulAuthentication(request, user);
		}
		......

		cancelCookie(request, response);
		return null;
	}

Spring Security就是在這里提取出 cookie 信息,并對 cookie 信息進行解碼。解碼之后,再調用 processAutoLoginCookie()方法去做校驗。processAutoLoginCookie() 方法的代碼我就不貼了,核心流程就是首先獲取用戶名和過期時間,再根據用戶名查詢到用戶密碼,然后通過 MD5 散列函數計算出散列值。最后再將拿到的散列值和瀏覽器傳遞來的散列值進行對比,就能確認這個令牌是否有效,進而確認登錄是否有效。

至此,壹哥 就結合著源碼和底層原理,給大家講解了基于散列加密方案實現了自動登錄,并且在本案例中給大家介紹了散列加密算法,你掌握的怎么樣呢?請在評論區給 一一哥 留言,說說你的感受吧!下一篇文章中,壹哥 會給各位講解 基于持久化令牌方案實現自動登錄,敬請期待哦!

到此這篇關于Spring Security基于散列加密方案實現自動登錄功能的文章就介紹到這了,更多相關Spring Security散列加密方案實現自動登錄內容請搜索服務器之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持服務器之家!

原文鏈接:https://blog.csdn.net/syc000666/article/details/120481624

延伸 · 閱讀

精彩推薦
  • Java教程Java使用SAX解析xml的示例

    Java使用SAX解析xml的示例

    這篇文章主要介紹了Java使用SAX解析xml的示例,幫助大家更好的理解和學習使用Java,感興趣的朋友可以了解下...

    大行者10067412021-08-30
  • Java教程小米推送Java代碼

    小米推送Java代碼

    今天小編就為大家分享一篇關于小米推送Java代碼,小編覺得內容挺不錯的,現在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧...

    富貴穩中求8032021-07-12
  • Java教程20個非常實用的Java程序代碼片段

    20個非常實用的Java程序代碼片段

    這篇文章主要為大家分享了20個非常實用的Java程序片段,對java開發項目有所幫助,感興趣的小伙伴們可以參考一下 ...

    lijiao5352020-04-06
  • Java教程Java BufferWriter寫文件寫不進去或缺失數據的解決

    Java BufferWriter寫文件寫不進去或缺失數據的解決

    這篇文章主要介紹了Java BufferWriter寫文件寫不進去或缺失數據的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望...

    spcoder14552021-10-18
  • Java教程升級IDEA后Lombok不能使用的解決方法

    升級IDEA后Lombok不能使用的解決方法

    最近看到提示IDEA提示升級,尋思已經有好久沒有升過級了。升級完畢重啟之后,突然發現好多錯誤,本文就來介紹一下如何解決,感興趣的可以了解一下...

    程序猿DD9332021-10-08
  • Java教程Java8中Stream使用的一個注意事項

    Java8中Stream使用的一個注意事項

    最近在工作中發現了對于集合操作轉換的神器,java8新特性 stream,但在使用中遇到了一個非常重要的注意點,所以這篇文章主要給大家介紹了關于Java8中S...

    阿杜7482021-02-04
  • Java教程xml與Java對象的轉換詳解

    xml與Java對象的轉換詳解

    這篇文章主要介紹了xml與Java對象的轉換詳解的相關資料,需要的朋友可以參考下...

    Java教程網2942020-09-17
  • Java教程Java實現搶紅包功能

    Java實現搶紅包功能

    這篇文章主要為大家詳細介紹了Java實現搶紅包功能,采用多線程模擬多人同時搶紅包,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙...

    littleschemer13532021-05-16
主站蜘蛛池模板: 国内精品国语自产拍在线观看55 | 久久综合狠狠综合久久综合88 | 韩国久播影院理论片不卡影院 | 亚洲成人免费观看 | 精品亚洲视频在线观看 | 日韩欧美中文字幕一区 | 国产成人综合网亚洲欧美在线 | 国产极品美女在线 | 日韩免费高清完整版 | 国产乱码一卡二卡3卡四卡 国产乱插 | 热国产热综合 | 久久久这里有精品999 | 黑人巨大精品战中国美女 | 欧美日韩国内 | 欧美粗黑巨大gay | www.大逼色 | 国产免费好大好硬视频 | pron欧美| 国产综合成色在线视频 | 88av视频在线观看 | 色ccc36| 青青草视频破解版 | 小向美奈子av| 2020韩国三级理论在线观看 | 国产一区二区三区水野朝阳 | 亚洲欧美在线观看首页 | 亚洲欧美专区精品久久 | 从后面撕开老师的丝袜动态图 | 999热在线精品观看全部 | 日韩毛片在线影视 | 99久热只有精品视频免费观看17 | 亚洲 欧美 另类 中文 在线 | 国产美女做爰免费视频网址 | 午夜伦午夜伦锂电影 | 国产人成精品午夜在线观看 | 性欧美金发洋妞xxxxbbbb | 女八把屁股扒开让男生添 | 日本动漫黄网站在线观看 | 明星ai人脸替换造梦在线播放 | 1717she精品视频在线观看 | 国产在线精品观看 |