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

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

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

服務器之家 - 編程語言 - Java教程 - SpringBoot 配合 SpringSecurity 實現自動登錄功能的代碼

SpringBoot 配合 SpringSecurity 實現自動登錄功能的代碼

2020-09-27 14:40南淮北安 Java教程

這篇文章主要介紹了SpringBoot 配合 SpringSecurity 實現自動登錄功能的代碼,代碼簡單易懂,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下

自動登錄是我們在軟件開發時一個非常常見的功能,例如我們登錄 QQ 郵箱:

SpringBoot 配合 SpringSecurity 實現自動登錄功能的代碼

很多網站我們在登錄的時候都會看到類似的選項,畢竟總讓用戶輸入用戶名密碼是一件很麻煩的事。

自動登錄功能就是,用戶在登錄成功后,在某一段時間內,如果用戶關閉了瀏覽器并重新打開,或者服務器重啟了,都不需要用戶重新登錄了,用戶依然可以直接訪問接口數據

作為一個常見的功能,我們的 Spring Security 肯定也提供了相應的支持,本文我們就來看下 Spring Security 中如何實現這個功能。

 

一、加入 remember-me

 

為了配置方便,加入兩個依賴即可:

SpringBoot 配合 SpringSecurity 實現自動登錄功能的代碼

配置類中添加如下代碼:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
  @Bean
  PasswordEncoder passwordEncoder(){
    return NoOpPasswordEncoder.getInstance();
  }
 
  @Override
  protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.inMemoryAuthentication()
        .withUser("yolo")
        .password("123").roles("admin");
  }
 
  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
        .anyRequest().authenticated()
        .and()
        .formLogin()
        .and()
        .rememberMe()
        .and()
        .csrf().disable();
  }
}

大家看到,這里只需要添加一個 .rememberMe() 即可,自動登錄功能就成功添加進來了。

接下來我們隨意添加一個測試接口:

?
1
2
3
4
5
6
7
@RestController
public class HelloController {
  @GetMapping("/hello")
  public String hello(){
    return "Hello Yolo !!!";
  }
}

SpringBoot 配合 SpringSecurity 實現自動登錄功能的代碼

這個時候大家發現,默認的登錄頁面多了一個選項,就是記住我。我們輸入用戶名密碼,并且勾選上記住我這個框,然后點擊登錄按鈕執行登錄操作。

可以看到,登錄數據中,除了 username 和 password 之外,還有一個 remember-me,之所以給大家看這個,是想告訴大家,如果你你需要自定義登錄頁面,RememberMe 這個選項的 key 該怎么寫。

登錄成功之后,就會自動跳轉到 hello 接口了。我們注意,系統訪問 hello 接口的時候,攜帶的 cookie:

SpringBoot 配合 SpringSecurity 實現自動登錄功能的代碼

大家注意到,這里多了一個 remember-me,這就是這里實現的核心,關于這個 remember-me 我一會解釋,我們先來測試效果。

接下來,我們關閉瀏覽器,再重新打開瀏覽器。正常情況下,瀏覽器關閉再重新打開,如果需要再次訪問 hello 接口,就需要我們重新登錄了。但是此時,我們再去訪問 hello 接口,發現不用重新登錄了,直接就能訪問到,這就說明我們的 RememberMe 配置生效了(即下次自動登錄功能生效了)。

 

二、原理分析

 

按理說,瀏覽器關閉再重新打開,就要重新登錄,現在竟然不用等了,那么這個功能到底是怎么實現的呢?

首先我們來分析一下 cookie 中多出來的這個 remember-me,這個值一看就是一個 Base64 轉碼后的字符串,我們可以使用網上的一些在線工具來解碼,可以自己簡單寫兩行代碼來解碼:

?
1
2
3
4
5
6
@Test
  void contextLoads() {
    String s = new String(
        Base64.getDecoder().decode("eW9sbzoxNjAxNDczNTY2NTA1OjlmMGY5YjBjOTAzYmNjYmU3ZjMwYWM0NjVlZjEzNmQ5"));
    System.out.println("s = " + s);
  }

執行這段代碼,輸出結果如下:

s = yolo:1601473566505:9f0f9b0c903bccbe7f30ac465ef136d9

可以看到,這段 Base64 字符串實際上用 : 隔開,分成了三部分:

(1)第一段是用戶名,這個無需質疑。
(2)第二段看起來是一個時間戳,我們通過在線工具或者 Java 代碼解析后發現,這是一個兩周后的數據。
(3)第三段我就不賣關子了,這是使用 MD5 散列函數算出來的值,他的明文格式是
username + ":" + tokenExpiryTime + ":" + password + ":" + key,最后的 key 是一個散列鹽值,可以用來防治令牌被修改。

了解到 cookie 中 remember-me 的含義之后,那么我們對于記住我的登錄流程也就很容易猜到了了。

在瀏覽器關閉后,并重新打開之后,用戶再去訪問 hello 接口,此時會攜帶著 cookie 中的 remember-me 到服務端,服務到拿到值之后,可以方便的計算出用戶名和過期時間,再根據用戶名查詢到用戶密碼,然后通過 MD5 散列函數計算出散列值,再將計算出的散列值和瀏覽器傳遞來的散列值進行對比,就能確認這個令牌是否有效。

流程就是這么個流程,接下來我們通過分析源碼來驗證一下這個流程對不對。

三、源碼分析

接下來,我們通過源碼來驗證一下我們上面說的對不對。

這里主要從兩個方面來介紹,一個是 remember-me 這個令牌生成的過程,另一個則是它解析的過程。

1. 生成

生成的核心處理方法在:TokenBasedRememberMeServices#onLoginSuccess:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@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();
 }
 int tokenLifetime = calculateLoginLifetime(request, successfulAuthentication);
 long expiryTime = System.currentTimeMillis();
 expiryTime += 1000L * (tokenLifetime < 0 ? TWO_WEEKS_S : tokenLifetime);
 String signatureValue = makeTokenSignature(expiryTime, username, password);
 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 字符串。
(5)最后,將用戶名、令牌有效期以及計算得到的散列值放入 Cookie 中。

關于第四點,我這里再說一下。

由于我們自己沒有設置 key,key 默認值是一個 UUID 字符串,這樣會帶來一個問題,就是如果服務端重啟,這個 key 會變,這樣就導致之前派發出去的所有 remember-me 自動登錄令牌失效,所以,我們可以指定這個 key。指定方式如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
@Override
protected void configure(HttpSecurity http) throws Exception {
  http.authorizeRequests()
      .anyRequest().authenticated()
      .and()
      .formLogin()
      .and()
      .rememberMe()
      .key("yolo")
      .and()
      .csrf().disable();
}

如果自己配置了 key,即使服務端重啟,即使瀏覽器打開再關閉,也依然能夠訪問到 hello 接口

這是 remember-me 令牌生成的過程。至于是如何走到 onLoginSuccess 方法的,這里可以給大家稍微提醒一下思路:

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

2. 解析

那么當用戶關掉并打開瀏覽器之后,重新訪問 /hello 接口,此時的認證流程又是怎么樣的呢?

我們之前說過,Spring Security 中的一系列功能都是通過一個過濾器鏈實現的,RememberMe 這個功能當然也不例外。

Spring Security 中提供了 RememberMeAuthenticationFilter 類專門用來做相關的事情,我們來看下 RememberMeAuthenticationFilterdoFilter 方法:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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) {
  rememberMeAuth = authenticationManager.authenticate(rememberMeAuth);
  SecurityContextHolder.getContext().setAuthentication(rememberMeAuth);
  onSuccessfulAuthentication(request, response, rememberMeAuth);
  if (this.eventPublisher != null) {
   eventPublisher
    .publishEvent(new InteractiveAuthenticationSuccessEvent(
     SecurityContextHolder.getContext()
      .getAuthentication(), this.getClass()));
  }
  if (successHandler != null) {
   successHandler.onAuthenticationSuccess(request, response,
    rememberMeAuth);
   return;
  }
  }
 chain.doFilter(request, response);
 }
 else {
 chain.doFilter(request, response);
 }
}

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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);
 }
 catch (CookieTheftException cte) {
 
 throw cte;
 }
 cancelCookie(request, response);
 return null;
}

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

四、總結

看了上面的文章,大家可能已經發現,如果我們開啟了 RememberMe 功能,最最核心的東西就是放在 cookie 中的令牌了,這個令牌突破了 session 的限制,即使服務器重啟、即使瀏覽器關閉又重新打開,只要這個令牌沒有過期,就能訪問到數據。

一旦令牌丟失,別人就可以拿著這個令牌隨意登錄我們的系統了,這是一個非常危險的操作。

但是實際上這是一段悖論,為了提高用戶體驗(少登錄),我們的系統不可避免的引出了一些安全問題,不過我們可以通過技術將安全風險降低到最小

到此這篇關于SpringBoot 配合 SpringSecurity 實現自動登錄功能的代碼的文章就介紹到這了,更多相關SpringSecurity自動登錄內容請搜索服務器之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持服務器之家!

原文鏈接:https://blog.csdn.net/nanhuaibeian/article/details/108630616

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 火影小南被爆羞羞网站 | 国内精品麻豆 | 国产一区日韩二区欧美三区 | 色综合视频一区二区观看 | 国产日韩一区二区三区 | 午夜国产精品福利在线观看 | 久久99亚洲AV无码四区碰碰 | 天天综合天天影视色香欲俱全 | 99re8在线精品视频免费播放 | 我年轻漂亮的继坶2中字在线播放 | 欧美人人干 | 国产福利不卡视频在免费 | 男人的视频网站 | 华人亚洲欧美精品国产 | 四虎成人国产精品视频 | 色老板视频在线 | 日本老妇乱子伦中文视频 | 欧美爽妇 | 91色视| 精品国产福利在线观看一区 | 好大好硬好紧太深了受不了 | 青青青视频蜜桃一区二区 | 欧美精品日韩一区二区三区 | 欧美成人三级伦在线观看 | 亚洲冬月枫中文字幕在线看 | 免费国产福利 | 国产欧美另类 | porno美国xxxx| 色视频综合| 波多野结衣中文字幕在线 | 精品久久久久久久国产潘金莲 | 日本视频二区 | 99久久九九 | 精品免费| 继的朋友无遮漫画免费观看73 | 亚洲电影第1页 | 久久亚洲精品AV无码四区 | 小小水蜜桃视频高清在线观看免费 | 非洲黑女人性xxxx | 91久久青青草原线免费 | 欧美ⅹxxxx视频 |