前端JWT token維護方案

一、需求描述:

前端登錄後,後端返回acToken和acToken有效時間以及refreshToken,然後在request.headers帶上acToken,當acToken過期時要用refreshToken去獲取新的acToken,當refreshToken過期前端跳轉到登錄頁,獲取新的acToken時要做到用戶無感知。

 二、分析

當用戶發起一個請求時,判斷acToken是否已過期,若已過期則先調refreshToken接口,拿到新的acToken後再繼續執行之前的請求。

這個問題的難點在於:

1、當同時發起多個請求,而刷新acToken的接口還沒返回,此時其他請求該如何處理?

2、假如請求中帶有用戶行爲,比如一個購買按鈕,點擊它會先發起用戶是否被禁用接口,如果沒有禁用,則跳轉到購買頁,否則彈窗提示。

三、方案

1、在請求發起前攔截每個請求,判斷acToken的有效時間是否已經過期,若已過期,則將請求掛起,先刷新acToken後再繼續請求。

  • 優點: 在請求前攔截,能節省請求,省流量。
  • 缺點: 需要後端額外提供一個acToken過期時間的字段;使用了本地時間判斷,若本地時間被篡改,特別是本地時間比服務器時間慢時,攔截會失敗。
PS:acToken有效時間建議是時間段,類似緩存的MaxAge,而不要是絕對時間。當服務器和本地時間不一致時,絕對時間會有問題。

2、不在請求前攔截,而是攔截返回後的數據。先發起請求,接口返回過期後,先刷新acToken,再進行一次重試。

  • 優點:不需額外的acToken過期字段,不需判斷時間。
  • 缺點: 1、會消耗多一次請求,耗流量 2、請求then之後有用戶行爲,無法執行。
     

因爲方案2沒有解決到難點2,故此使用方案1,如有辦法再研究

 

四、實現代碼如下:

let isRefreshing = false
let requests =[]
function getJwtTokenVO(){
    return localStorage.getItem('jwtTokenVO')?JSON.parse(localStorage.getItem('jwtTokenVO')):''
}
// request interceptor
service.interceptors.request.use(
    config => {
        // 過濾掉刷新token接口(避免死循環!!!!)和登錄接口
  if (config.url.indexOf('/refreshToken') >= 0 || config.url.indexOf('/login') >= 0) {
            return config
        }
        const jwtTokenVO = getJwtTokenVO()
        if (jwtTokenVO) {
            config.headers={...config.headers,acToken:jwtTokenVO.accessToken}
            const now = new Date().getTime()
            const accessTokenExpire = jwtTokenVO.accessTokenExpire //這個是絕對時間戳
  // 穩妥要提前一點時間刷新token
  if (now > (accessTokenExpire-5000)) {
                // 立即刷新token
  if (!isRefreshing) {
                    console.log('正在刷新token')
                    isRefreshing = true
  fetchRefreshToken({ token: jwtTokenVO.refreshToken }).then(resData => {
                        // 刷新token接口的時候可能引起refreshToken過期跳轉登錄頁,這裏沒有進service.interceptors.response
  if (resData.code * 1 === 5004) {
                            localStorage.removeItem('jwtTokenVO')
                            let href = window.location.href
  /* refreshToken失效 這個code需要再修改 */
  window.location.href = `${location.origin}${location.pathname}${location.search}#/login?orgId=${localStorage.getItem('lc_orgId')}&next=${href}`
  return ''
  }
                        if (resData.code * 1 === 1) {
                            console.log('重新獲取accessToken 成功')
                            /* 重新賦值accessToken */
  localStorage.setItem('jwtTokenVO', JSON.stringify(resData.data))
                            axios.defaults.headers['acToken'] = resData.data.accessToken
  }
                        isRefreshing = false
 return resData.data.accessToken
  }).then((accessToken) => {
                        console.log('刷新token成功,執行請求隊列')
                        if(accessToken){
                            requests.forEach(cb => cb(accessToken))
                            // 執行完成後,清空隊列
  requests = []
                        }
                    }).catch(res => {
                        console.error('刷新token error: ', res)
                    })
                }
                const requestList = new Promise((resolve) => {
                    requests.push((accessToken) => {
                        // 刷新config的舊token
  config.headers['acToken'] = accessToken
                        resolve(config)
                    })
                })
                return requestList
                //
  }
        }
        return config
    },
  error => {
        console.log(error) // for debug
  return Promise.reject(error)
    }
)
// 請求返回後攔截
instance.interceptors.response.use(response => {
    const { code } = response.data
  if (code === 5004) {
        localStorage.removeItem('jwtTokenVO')
        let href = window.location.href
  /* refreshToken失效 這個code需要再修改 */
  window.location.href = `${location.origin}${location.pathname}${location.search}#/login?orgId=${localStorage.getItem('lc_orgId')}&next=${href}`
  }
    return response
}, error => {
    console.log('catch', error)
    return Promise.reject(error)
})

參考鏈接:https://segmentfault.com/a/1190000020986592?utm_source=tag-newest

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章