java實(shí)現(xiàn)token無(wú)感刷新+處理并發(fā)的后端方案
問(wèn)題描述:
當(dāng)用戶(hù)通過(guò)登陸后進(jìn)入一個(gè)web網(wǎng)站,會(huì)把token保存到localStorage。假設(shè)token過(guò)期時(shí)間30min。
那么當(dāng)用戶(hù)在網(wǎng)站快樂(lè)地玩耍了30min后,這時(shí)進(jìn)行了一次提交表單,它會(huì)被重定向到登陸頁(yè)面。
作為用戶(hù):我表單填了這么久,點(diǎn)擊提交時(shí)讓我重新登陸?我的體驗(yàn)很不好。
jwt的好處和局限
jwt的好處是:服務(wù)器無(wú)需存儲(chǔ)這些登陸的狀態(tài)
而它的局限是:服務(wù)器無(wú)法主動(dòng)地通知用戶(hù)“你的token過(guò)期了,我重新給你一個(gè)”。
如何解決
方案一:雙token
在登陸時(shí)我們會(huì)生成倆個(gè)token
- token:表示正常情況下登陸憑證
- refresh-token:表示需要刷新情況下登陸憑證
假設(shè)前者(token)設(shè)置過(guò)期時(shí)間為30min,后者為1天。
流程
- time=0min,用戶(hù)成功登陸,后端返回倆個(gè)token(token和refresh-token),前端把它倆保存到localStorage
- time=10min,用戶(hù)進(jìn)行了一次查詢(xún),前端將倆個(gè)token都發(fā)給后端,后端檢驗(yàn)token,有效,返回結(jié)果,用戶(hù)很開(kāi)心!
- time=35min,用戶(hù)提交了表單,前端還是將倆個(gè)token發(fā)給后端;后端檢驗(yàn)token,過(guò)期;檢驗(yàn)refresh-token,有效,后端認(rèn)為這是要刷新token,生成新的token和refresh-token,成功返回?cái)?shù)據(jù)和雙token。前端把數(shù)據(jù)渲染,把雙token保存。
token正常過(guò)期的情況:
當(dāng)然,token可以正常過(guò)期,如果在檢驗(yàn)時(shí)refresh-token也過(guò)期,那就說(shuō)明正常過(guò)期
假設(shè)time=0min時(shí)用戶(hù)登陸,time=2天時(shí)用戶(hù)提交了表單,那么后端認(rèn)為這是正常過(guò)期,用戶(hù)需要重新登陸。
流程圖如下:
token刷新并發(fā)問(wèn)題
現(xiàn)在很多登陸是可以多端的,當(dāng)多端并發(fā)都去嘗試刷新token時(shí),會(huì)導(dǎo)致token被重復(fù)刷新
方案二:刷新時(shí)用分布式鎖
可以引入redis緩存中間件,使用緩存加速并處理并發(fā)刷新問(wèn)題。
將雙token生成后存入redis。當(dāng)需要刷新token時(shí),采用double-check+獲取關(guān)于refresh-token的分布式鎖來(lái)實(shí)現(xiàn)。
偽代碼如下:
// 驗(yàn)證和刷新Token的方法 public String validateAndRefreshToken(String userId, String accessToken, String refreshToken) { String storedAccessToken = redisTemplate.opsForValue().get(ACCESS_TOKEN_PREFIX + userId); String storedRefreshToken = redisTemplate.opsForValue().get(REFRESH_TOKEN_PREFIX + userId); // 檢查Access Token是否有效 if (storedAccessToken != null && storedAccessToken.equals(accessToken)) { return accessToken; // 直接返回原始的Access Token } // Access Token無(wú)效,檢查Refresh Token if (storedRefreshToken != null && storedRefreshToken.equals(refreshToken)) { // 使用雙層檢查和分布式鎖控制高并發(fā)刷新 String lockKey = "refresh_lock:" + userId; boolean lockAcquired = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 5, TimeUnit.SECONDS); if (lockAcquired) { try { // 再次雙層檢查(避免重復(fù)刷新) storedAccessToken = redisTemplate.opsForValue().get(ACCESS_TOKEN_PREFIX + userId); if (storedAccessToken == null) { // 生成新的Access Token String newAccessToken = generateToken(); redisTemplate.opsForValue().set(ACCESS_TOKEN_PREFIX + userId, newAccessToken, 15, TimeUnit.MINUTES); return newAccessToken; } else { return storedAccessToken; // 直接返回已刷新過(guò)的Token } } finally { redisTemplate.delete(lockKey); // 釋放鎖 } } else { // 未獲取到鎖,等待其他線程刷新完成,再次獲取已刷新的Access Token return redisTemplate.opsForValue().get(ACCESS_TOKEN_PREFIX + userId); } } // 若Refresh Token也無(wú)效,返回null或拋出異常,需重新登錄 throw new TokenInvalidException("Access and Refresh Token both expired."); }
方案三:過(guò)渡時(shí)間(沒(méi)看懂)
到此這篇關(guān)于java實(shí)現(xiàn)token無(wú)感刷新+處理并發(fā)的后端方案的文章就介紹到這了,更多相關(guān)java token無(wú)感刷新和并發(fā)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java遞歸基礎(chǔ)與遞歸的宏觀語(yǔ)意實(shí)例分析
這篇文章主要介紹了Java遞歸基礎(chǔ)與遞歸的宏觀語(yǔ)意,結(jié)合實(shí)例形式分析了java遞歸的相關(guān)原理、操作技巧與注意事項(xiàng),需要的朋友可以參考下2020-03-03Java.SE數(shù)組的一些常見(jiàn)練習(xí)題
數(shù)組可以看成是相同類(lèi)型元素的一個(gè)集合,在內(nèi)存中是一段連續(xù)的空間,這篇文章主要給大家介紹了關(guān)于Java.SE數(shù)組的一些常見(jiàn)練習(xí)題,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-02-02SpringBoot項(xiàng)目配置postgresql數(shù)據(jù)庫(kù)完整步驟(配置多數(shù)據(jù)源)
PostgreSQL是一種特性非常齊全的自由軟件的對(duì)象-關(guān)系型數(shù)據(jù)庫(kù)管理系統(tǒng)(ORDBMS),下面這篇文章主要給大家介紹了關(guān)于SpringBoot項(xiàng)目配置postgresql數(shù)據(jù)庫(kù)(配置多數(shù)據(jù)源)的相關(guān)資料,需要的朋友可以參考下2023-05-05MyBatis關(guān)聯(lián)查詢(xún)的實(shí)現(xiàn)
MyBatis可以通過(guò)定義多個(gè)表的關(guān)聯(lián)關(guān)系,實(shí)現(xiàn)多表查詢(xún),本文主要介紹了MyBatis關(guān)聯(lián)查詢(xún)的實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下2023-11-11Java創(chuàng)建對(duì)象(顯式創(chuàng)建和隱含創(chuàng)建)
本文詳細(xì)介紹對(duì)象的創(chuàng)建,在 Java 語(yǔ)言中創(chuàng)建對(duì)象分顯式創(chuàng)建與隱含創(chuàng)建兩種情況,顯式創(chuàng)建和隱含創(chuàng)建,,需要的朋友可以參考下面文章的具體內(nèi)容2021-09-09Spring和Websocket相結(jié)合實(shí)現(xiàn)消息的推送
這篇文章主要介紹了Spring和Websocket相結(jié)合實(shí)現(xiàn)消息的推送的相關(guān)資料,本文介紹的非常詳細(xì)具有參考借鑒價(jià)值,感興趣的朋友一起學(xué)習(xí)吧2016-02-02SpringBoot中的PropertySource原理詳解
這篇文章主要介紹了SpringBoot中的PropertySource原理詳解,PropertySource?是一個(gè)非常重要的概念,它允許您在應(yīng)用程序中定義屬性,并將這些屬性注入到?Spring?環(huán)境中,需要的朋友可以參考下2023-07-07