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

SpringBoot 配合 SpringSecurity 實(shí)現(xiàn)自動(dòng)登錄功能的代碼

 更新時(shí)間:2020年09月27日 09:14:23   作者:南淮北安  
這篇文章主要介紹了SpringBoot 配合 SpringSecurity 實(shí)現(xiàn)自動(dòng)登錄功能的代碼,代碼簡(jiǎn)單易懂,對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下

自動(dòng)登錄是我們?cè)谲浖_發(fā)時(shí)一個(gè)非常常見(jiàn)的功能,例如我們登錄 QQ 郵箱:

在這里插入圖片描述

很多網(wǎng)站我們?cè)诘卿浀臅r(shí)候都會(huì)看到類似的選項(xiàng),畢竟總讓用戶輸入用戶名密碼是一件很麻煩的事。

自動(dòng)登錄功能就是,用戶在登錄成功后,在某一段時(shí)間內(nèi),如果用戶關(guān)閉了瀏覽器并重新打開,或者服務(wù)器重啟了,都不需要用戶重新登錄了,用戶依然可以直接訪問(wèn)接口數(shù)據(jù)

作為一個(gè)常見(jiàn)的功能,我們的 Spring Security 肯定也提供了相應(yīng)的支持,本文我們就來(lái)看下 Spring Security 中如何實(shí)現(xiàn)這個(gè)功能。

一、加入 remember-me

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

在這里插入圖片描述

配置類中添加如下代碼:

@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();
 }
}

大家看到,這里只需要添加一個(gè) .rememberMe() 即可,自動(dòng)登錄功能就成功添加進(jìn)來(lái)了。

接下來(lái)我們隨意添加一個(gè)測(cè)試接口:

@RestController
public class HelloController {
 @GetMapping("/hello")
 public String hello(){
 return "Hello Yolo !!!";
 }
}

在這里插入圖片描述

這個(gè)時(shí)候大家發(fā)現(xiàn),默認(rèn)的登錄頁(yè)面多了一個(gè)選項(xiàng),就是記住我。我們輸入用戶名密碼,并且勾選上記住我這個(gè)框,然后點(diǎn)擊登錄按鈕執(zhí)行登錄操作。

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

登錄成功之后,就會(huì)自動(dòng)跳轉(zhuǎn)到 hello 接口了。我們注意,系統(tǒng)訪問(wèn) hello 接口的時(shí)候,攜帶的 cookie:

在這里插入圖片描述

大家注意到,這里多了一個(gè) remember-me,這就是這里實(shí)現(xiàn)的核心,關(guān)于這個(gè) remember-me 我一會(huì)解釋,我們先來(lái)測(cè)試效果。

接下來(lái),我們關(guān)閉瀏覽器,再重新打開瀏覽器。正常情況下,瀏覽器關(guān)閉再重新打開,如果需要再次訪問(wèn) hello 接口,就需要我們重新登錄了。但是此時(shí),我們?cè)偃ピL問(wèn) hello 接口,發(fā)現(xiàn)不用重新登錄了,直接就能訪問(wèn)到,這就說(shuō)明我們的 RememberMe 配置生效了(即下次自動(dòng)登錄功能生效了)。

二、原理分析

按理說(shuō),瀏覽器關(guān)閉再重新打開,就要重新登錄,現(xiàn)在竟然不用等了,那么這個(gè)功能到底是怎么實(shí)現(xiàn)的呢?

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

@Test
 void contextLoads() {
 String s = new String(
  Base64.getDecoder().decode("eW9sbzoxNjAxNDczNTY2NTA1OjlmMGY5YjBjOTAzYmNjYmU3ZjMwYWM0NjVlZjEzNmQ5"));
 System.out.println("s = " + s);
 }

執(zhí)行這段代碼,輸出結(jié)果如下:

s = yolo:1601473566505:9f0f9b0c903bccbe7f30ac465ef136d9

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

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

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

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

流程就是這么個(gè)流程,接下來(lái)我們通過(guò)分析源碼來(lái)驗(yàn)證一下這個(gè)流程對(duì)不對(duì)。

三、源碼分析

接下來(lái),我們通過(guò)源碼來(lái)驗(yàn)證一下我們上面說(shuō)的對(duì)不對(duì)。

這里主要從兩個(gè)方面來(lái)介紹,一個(gè)是 remember-me 這個(gè)令牌生成的過(guò)程,另一個(gè)則是它解析的過(guò)程。

1. 生成

生成的核心處理方法在: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();
 }
 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)由于登錄成功之后,密碼可能被擦除了,所以,如果一開始沒(méi)有拿到密碼,就再?gòu)?UserDetailsService 中重新加載用戶并重新獲取密碼。
(3)再接下來(lái)去獲取令牌的有效期,令牌有效期默認(rèn)就是兩周。
(4)再接下來(lái)調(diào)用 makeTokenSignature 方法去計(jì)算散列值,實(shí)際上就是根據(jù) username、令牌有效期以及 password、key 一起計(jì)算一個(gè)散列值。如果我們沒(méi)有自己去設(shè)置這個(gè) key,默認(rèn)是在 RememberMeConfigurer#getKey 方法中進(jìn)行設(shè)置的,它的值是一個(gè) UUID 字符串。
(5)最后,將用戶名、令牌有效期以及計(jì)算得到的散列值放入 Cookie 中。

關(guān)于第四點(diǎn),我這里再說(shuō)一下。

由于我們自己沒(méi)有設(shè)置 key,key 默認(rèn)值是一個(gè) UUID 字符串,這樣會(huì)帶來(lái)一個(gè)問(wèn)題,就是如果服務(wù)端重啟,這個(gè) key 會(huì)變,這樣就導(dǎo)致之前派發(fā)出去的所有 remember-me 自動(dòng)登錄令牌失效,所以,我們可以指定這個(gè) key。指定方式如下:

@Override
protected void configure(HttpSecurity http) throws Exception {
 http.authorizeRequests()
  .anyRequest().authenticated()
  .and()
  .formLogin()
  .and()
  .rememberMe()
  .key("yolo")
  .and()
  .csrf().disable();
}

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

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

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

2. 解析

那么當(dāng)用戶關(guān)掉并打開瀏覽器之后,重新訪問(wèn) /hello 接口,此時(shí)的認(rèn)證流程又是怎么樣的呢?

我們之前說(shuō)過(guò),Spring Security 中的一系列功能都是通過(guò)一個(gè)過(guò)濾器鏈實(shí)現(xiàn)的,RememberMe 這個(gè)功能當(dāng)然也不例外。

Spring Security 中提供了 RememberMeAuthenticationFilter 類專門用來(lái)做相關(guān)的事情,我們來(lái)看下 RememberMeAuthenticationFilterdoFilter 方法:

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);
 }
}

這個(gè)方法最關(guān)鍵的地方在于,如果從 SecurityContextHolder 中無(wú)法獲取到當(dāng)前登錄用戶實(shí)例,那么就調(diào)用 rememberMeServices.autoLogin 邏輯進(jìn)行登錄,我們來(lái)看下這個(gè)方法:

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 信息,并對(duì) cookie 信息進(jìn)行解碼,解碼之后,再調(diào)用 processAutoLoginCookie 方法去做校驗(yàn),processAutoLoginCookie 方法的代碼我就不貼了,核心流程就是首先獲取用戶名和過(guò)期時(shí)間,再根據(jù)用戶名查詢到用戶密碼,然后通過(guò) MD5 散列函數(shù)計(jì)算出散列值,再將拿到的散列值和瀏覽器傳遞來(lái)的散列值進(jìn)行對(duì)比,就能確認(rèn)這個(gè)令牌是否有效,進(jìn)而確認(rèn)登錄是否有效。

三、總結(jié)

看了上面的文章,大家可能已經(jīng)發(fā)現(xiàn),如果我們開啟了 RememberMe 功能,最最核心的東西就是放在 cookie 中的令牌了,這個(gè)令牌突破了 session 的限制,即使服務(wù)器重啟、即使瀏覽器關(guān)閉又重新打開,只要這個(gè)令牌沒(méi)有過(guò)期,就能訪問(wèn)到數(shù)據(jù)。

一旦令牌丟失,別人就可以拿著這個(gè)令牌隨意登錄我們的系統(tǒng)了,這是一個(gè)非常危險(xiǎn)的操作。

但是實(shí)際上這是一段悖論,為了提高用戶體驗(yàn)(少登錄),我們的系統(tǒng)不可避免的引出了一些安全問(wèn)題,不過(guò)我們可以通過(guò)技術(shù)將安全風(fēng)險(xiǎn)降低到最小

到此這篇關(guān)于SpringBoot 配合 SpringSecurity 實(shí)現(xiàn)自動(dòng)登錄功能的代碼的文章就介紹到這了,更多相關(guān)SpringSecurity自動(dòng)登錄內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • SpringBoot+Redis實(shí)現(xiàn)分布式緩存的方法步驟

    SpringBoot+Redis實(shí)現(xiàn)分布式緩存的方法步驟

    在高并發(fā)的分布式的系統(tǒng)中,緩存是提升系統(tǒng)性能的重要手段,本文主要介紹了SpringBoot+Redis實(shí)現(xiàn)分布式緩存的方法步驟,具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-07-07
  • 使用Spring組合自定義的注釋 mscharhag操作

    使用Spring組合自定義的注釋 mscharhag操作

    這篇文章主要介紹了使用Spring組合自定義的注釋 mscharhag,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的工作或?qū)W習(xí)有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-03-03
  • Java實(shí)現(xiàn)瀏覽器大文件上傳的示例詳解

    Java實(shí)現(xiàn)瀏覽器大文件上傳的示例詳解

    文件上傳是許多項(xiàng)目都有的功能,用戶上傳小文件速度一般都很快,但如果是大文件幾個(gè)g,幾十個(gè)g的時(shí)候,上傳了半天,馬上就要完成的時(shí)候,網(wǎng)絡(luò)波動(dòng)一下,文件又要重新上傳,所以本文給大家介紹了Java實(shí)現(xiàn)瀏覽器大文件上傳的示例,需要的朋友可以參考下
    2024-07-07
  • java+SpringBoot設(shè)計(jì)實(shí)現(xiàn)評(píng)教系統(tǒng)

    java+SpringBoot設(shè)計(jì)實(shí)現(xiàn)評(píng)教系統(tǒng)

    這篇文章主要介紹了用過(guò)Java SpringBoot實(shí)現(xiàn)一個(gè)簡(jiǎn)單的評(píng)價(jià)系統(tǒng),可以做到學(xué)生管理、教師管理、評(píng)教管理、指標(biāo)管理、課程管理等。感興趣的可以了解一下
    2021-12-12
  • 基于java配置nginx獲取真實(shí)IP代碼實(shí)例

    基于java配置nginx獲取真實(shí)IP代碼實(shí)例

    這篇文章主要介紹了基于java配置nginx獲取真實(shí)IP代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-09-09
  • Java中關(guān)于控制臺(tái)讀取數(shù)字或字符串的方法

    Java中關(guān)于控制臺(tái)讀取數(shù)字或字符串的方法

    下面小編就為大家?guī)?lái)一篇Java中關(guān)于控制臺(tái)讀取數(shù)字或字符串的方法。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2016-10-10
  • java的泛型你真的了解嗎

    java的泛型你真的了解嗎

    這篇文章主要為大家詳細(xì)介紹了java的泛型,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助
    2022-03-03
  • 詳解Java中List的正確的刪除方法

    詳解Java中List的正確的刪除方法

    這篇文章主要為大家詳細(xì)介紹了Java中List的正確的刪除方法,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)有一定幫助,需要的可以參考一下
    2022-05-05
  • MyBatis-Plus中靜態(tài)工具Db的多種用法及實(shí)例分析

    MyBatis-Plus中靜態(tài)工具Db的多種用法及實(shí)例分析

    本文將詳細(xì)講解MyBatis-Plus中靜態(tài)工具Db的各種用法,并結(jié)合具體案例進(jìn)行演示和說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2025-03-03
  • java并發(fā)學(xué)習(xí)-CountDownLatch實(shí)現(xiàn)原理全面講解

    java并發(fā)學(xué)習(xí)-CountDownLatch實(shí)現(xiàn)原理全面講解

    這篇文章主要介紹了java并發(fā)學(xué)習(xí)-CountDownLatch實(shí)現(xiàn)原理全面講解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2021-02-02

最新評(píng)論

河津市| 洛浦县| 合江县| 肥城市| 乌海市| 孝昌县| 长垣县| 巩义市| 镇巴县| 彰化市| 安多县| 泾川县| 邵阳市| 蕉岭县| 清流县| 台东县| 三台县| 泸州市| 溧水县| 彭水| 农安县| 云南省| 贵州省| 雅江县| 平阳县| 峨眉山市| 武冈市| 澎湖县| 合肥市| 出国| 乌鲁木齐市| 繁昌县| 霍城县| 阜新市| 孝昌县| 庆城县| 滦平县| 安达市| 周口市| 台北县| 额尔古纳市|