記得上一篇Spring Cloud的文章關於如何使JWT失效進行了理論結合代碼實踐的說明,想固然的覺得那篇會是基於Spring Cloud統一認證架構系列的最終篇。但關於JWT另外還有一個熱議的話題是JWT續期?。html
本篇就我的以爲比較好的JWT續期方案以及落地和你們分享一下,算是拋轉引玉,你們有好的方案歡迎留言哈。前端
後端vue
管理前端ios
微信小程序git
理論背景: 在 有來商城 微服務項目 OAuth2實現微服務的統一認證的背景下,前端調用/oauth/token接口認證,在認證成功會返回兩個令牌access_token和refresh_token,出於安全考慮access_token時效相較refresh_token短不少(access_token默認12小時,refresh_token默認30天)。當access_token過時或者將要過時時,須要拿refresh_token去刷新獲取新的access_token返回給客戶端,可是爲了客戶良好的體驗須要作到無感知刷新。github
方案一:spring
瀏覽器起一個定時輪詢任務,每次在access_token過時以前刷新。axios
方案二:小程序
請求時返回access_token過時的異常時,瀏覽器發出一次使用refresh_token換取access_token的請求,獲取到新的access_token以後,重試因access_token過時而失敗的請求。後端
方案比較:
第一種方案實現簡單,但在access_token過時以前刷新,那些舊access_token依然可以有效訪問,若是使用黑名單的方式限制這些就的access_token無疑是在浪費資源。
第二種方案是在access_token已經失效的狀況下才去刷新便不會有上面的問題,可是它會多出來一次請求,並且實現起來考慮的問題相較下比較多,例如在token刷新階段後面來的請求如何處理,等獲取到新的access_token以後怎麼從新重試這些請求。
總結:第一種方案實現簡單;第二種方案更爲嚴謹,過時續期不會形成已被刷掉的access_token還有效;總之二者都是可行方案,本篇就第二種方案如何經過先後端的配合實現無感知刷新token實現JWT續期展開說明。
直接進入主題,如何經過代碼實如今access_token過時時使用refresh_token刷新續期,本篇涉及代碼基於Spring Cloud後端youlai-mall 和 Vue前端 youlai-mall-admin,須要的小夥伴能夠下載到本地參考下,若是對你有幫助,也但願給個star,感謝~
後端部分這裏惟一工做是在網關youlai-gateway鑑定access_token過時時拋出一個自定義異常提供給前端斷定,以下圖所示:
小夥伴們在這裏也許會有疑問,網關這裏如何判斷JWT是否已過時?先不急,下文會說明,先看實現以後再說原理。
設置OAuth2客戶端支持刷新模式,只有這樣才能使用refresh_token刷新換取新的access_token。以及爲了方便咱們測試分別設置access_token和refresh_token的過時時間,由於默認的12小時和30天咱們吃不消的;除此以外,還必須知足t(refresh_token) > 60s + t(access_token)的條件, refresh_token的時效大於access_token時效咱們能夠理解,那這個60s是怎麼回事,別急仍是先看實現,緣由下文會說明。
設置了支持客戶端刷新模式以後,在前端添加一個refreshToken方法,調用的接口和登陸認證是同一個接口/oauth/token,只是參數受權方式grant_type的值由password切換到refresh_token,即密碼模式切換到刷新模式,這個方法做用是在刷新token以後將新的token寫入到localStorage覆蓋舊的token。
在判斷響應結果是token過時時,執行刷新令牌方法覆蓋本地的token。
在刷新期間需作到兩點,一是避免重複刷新,二是請求重試,爲了知足以上兩點添加了兩個關鍵變量:
在第一次access_token過時請求失敗時,調用刷新token請求時開啓此標識,標識當前正在刷新中,避免後續請求因token失效重複刷新。
當執行刷新token期間時,須要把後來的請求先緩存到等待隊列,在刷新token成功時,從新執行等待隊列的請求便可。
修改請求響應封裝request.js的代碼以下,關鍵部分使用註釋說明,完整工程 youlai-mall-admin
let refreshing = false,// 正在刷新標識,避免重複刷新 waitQueue = [] // 請求等待隊列 service.interceptors.response.use( response => { const {code, msg, data} = response.data if (code !== '00000') { if (code === 'A0230') { // access_token過時 使用refresh_token刷新換取access_token const config = response.config if (refreshing == false) { refreshing = true const refreshToken = getRefreshToken() return store.dispatch('user/refreshToken', refreshToken).then((token) => { config.headers['Authorization'] = 'Bearer ' + token config.baseURL = '' // 請求重試時,url已包含baseURL waitQueue.forEach(callback => callback(token)) // 已刷新token,全部隊列中的請求重試 waitQueue = [] return service(config) }).catch(() => { // refresh_token也過時,直接跳轉登陸頁面從新登陸 MessageBox.confirm('當前頁面已失效,請從新登陸', '確認退出', { confirmButtonText: '從新登陸', cancelButtonText: '取消', type: 'warning' }).then(() => { store.dispatch('user/resetToken').then(() => { location.reload() }) }) }).finally(() => { refreshing = false }) } else { // 正在刷新token,返回未執行resolve的Promise,刷新token執行回調 return new Promise((resolve => { waitQueue.push((token) => { config.headers['Authorization'] = 'Bearer ' + token config.baseURL = '' // 請求重試時,url已包含baseURL resolve(service(config)) }) })) } } else { Message({ message: msg || '系統出錯', type: 'error', duration: 5 * 1000 }) } } return {code, msg, data} }, error => { return Promise.reject(error) } )
完成上面先後端代碼調整以後,接下來進入測試,還記得上面設置access_token時效爲1s、refresh_token爲120s吧。這裏access_token設置爲1s,可是時效確是61s,至於緣由下文細說。這裏把測試根據時間分爲3個階段:
聲明: 問題基於youlai-mall項目使用的nimbus-jose-jwt這個JWT庫,依賴spring-security-oauth2-jose這個jar包。
1. 如何斷定JWT過時?
JWT的是否過時判斷最終落點是在JwtTimestampValidator#validate方法上
2.爲何access_token比設定多了60s時效?
開掛?有後臺?向天再借60s?
剛開始在不知情的狀況下覺得本身哪裏配置錯了,設置5s過時,等個1min多鍾。後來確實沒辦法決心去調試下源碼,最後找到JWT驗證過時的方法JwtTimestampValidator#validate
基本上知足 Instant.now(this.clock).minus(this.clockSkew).isAfter(expiry)
就說明JWT過時了
now - 60s > expiry =轉換=> now > expiry + 60s
按正常理解當前時間大於過時時間就可斷定爲過時,但這裏卻在過時時間加了個時鐘偏移60s,活生生的延長了一分鐘,至於爲何?沒找到說明文檔,註釋也沒說明,知道的小夥伴歡迎下方留言~
本篇講述 youlai-mall 項目中如何經過先後端配合利用雙token刷新實現JWT續期的功能需求,後端拋出token過時異常,前端捕獲以後調用刷新token請求,成功則完成續期,失敗(通常指refresh_token也過時了)則須要從新登陸。在代碼的實現過程當中瞭解到在資源服務器(youlai-gateway)如何判斷JWT是否過時、axios如何進行請求重試等一些問題。
最後說一下本身的項目,youlai-mall 集成當前主流開發模式微服務加先後端分離,當前最新主流技術棧 Spring Cloud + Spring Cloud Alibaba + Vue , 以及最流行統一認證受權Spring Cloud Gateway + Spring Security OAuth2 + JWT。因此以爲本文對你有所幫助的話給個關注(持續更新中...),或者對該項目感興趣的小夥伴給個star,也期待你的加入和建議,仍是老樣子有問題隨時聯繫我~(微信號:haoxianrui)。
項目名稱 | 地址 |
---|---|
後臺 | youlai-mall |
管理前端 | youlai-mall-admin |
微信小程序 | youlai-mall-weapp |