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