Token過期處理

Token用于進行接口鑒權吓著,但是Token具有由后端設置的過期時間偏化,當Token過期以后,就無法再請求數(shù)據(jù)了
項目中后端設置的過期時間為24h,測試時我們可以手動修改token值讓Token失效
處理方式:

  • 方式1:用戶重新登錄,獲得新的Token就可以了贤笆,但是當過期時間較短的時候,每次都是要重新登錄操作 的,體驗很差
    • 為了提高用戶的信息安全性捅暴,Token的過期時間都比較短(就算萬一泄露了,過一會兒也就過期無效化了)
  • 方式2:根據(jù)用戶信息咧纠,自動給用戶生成新的Token蓬痒,減少登錄次數(shù)

我們觀察前面的功能的話,接口的響應信息中是有三個和token相關的信息的

  • access_token:當前使用的token漆羔,用于訪問需要授權的接口
  • expires_in:access_token的過期時間
  • refresh_token:刷新獲取新的access_token
    刷新Token 的方法有兩種:
    方法一:
    在每個請求發(fā)起前進行攔截梧奢,根據(jù)expires_in判斷token是否過期,如果過期則會刷新后再繼續(xù)請求接口
    • 優(yōu)點:請求前攔截處理演痒,能節(jié)省請求次數(shù)
    • 缺點:后端需要提供Token過期時間字段(例如:expires_in)亲轨,并且需要結合計算機本地時間判斷,如果計算機時間被篡改(特別是比服務器時間滿)時鸟顺,攔截會失敗的
      方法二:
      在每個請求響應后進行攔截惦蚊,如果發(fā)現(xiàn)請求失敗(Token過期導致的)時讯嫂,刷新Token再刷新請求接口
    • 優(yōu)點:無需Token過期時間字段养筒,無需判斷時間
    • 缺點:多消耗一次請求
      這里推薦使用方法二,相比較下來端姚,方法二更加的穩(wěn)定晕粪,不會出現(xiàn)意外的問題

Axios響應攔截器與錯誤處理

響應攔截器會在響應接收完畢,在對應請求處理前被攔截器攔截渐裸,響應攔截器參數(shù)response中保存了相應的信息

// Axios 官方文檔:響應攔截器
// Add a response interceptor
axios.interceptors.response.use(function (response) {
  // Any status code that lie within the range of 2xx cause this function to trigger
  // Do something with response data
  return response;
}, function (error) {
  // Any status codes that falls outside the range of 2xx cause this function to trigger
  // Do something with response error
  return Promise.reject(error);
});

那么我們接來下將響應攔截器設置到utils/request.js中巫湘,將axios更改為創(chuàng)建的request(因為我們使用了ESLint規(guī)范装悲,記得去除所有的分號)

  • error是需要console.dir()輸出的
// utils/request.js
...
// 設置響應攔截器
request.interceptors.response.use(function (response) {
  // 狀態(tài)碼為 2xx 都會進入這里
  console.log('請求響應成功了:', response)
  return response
}, function (error) {
  // 超出 2xx 都會進入這里
  console.dir(error)
  return Promise.reject(error)
})
export default request

Axios錯誤處理

錯誤處理,需要在攔截器中找到特定的錯誤情況進行token刷新
當出現(xiàn)錯誤時尚氛,通過Elemnt的Message組件設置提示诀诊,這里我們采用的是引入方式操作

  • 引入的Message與之前使用的this.$message是相同的,只是引入方式與操作方式不同
// 通過局部引入的方式阅嘶,引入Element的Message組件功能
import { Message } from 'element-ui'

// 響應攔截器
request.interceptors.response.use(function (response) {
  // 狀態(tài)碼2xx會執(zhí)行這里
  console.log('響應成功了', response)
  return response
}, function (error) {
  if (error.response) {
    // 請求發(fā)送成功属瓣,響應接收完畢,但是狀態(tài)碼為失敗的情況
    // 1.判斷失敗的狀態(tài)碼情況(主要處理401的情況)
    const { status } = error.response
    let errorMessage = ''
    if (status === 400) {
      errorMessage = '請求參數(shù)錯誤'
    } else if (status === 401) {
      // 2.Token無效(過期)處理
      errorMessage = 'Token 無效'
    } else if (status === 403) {
      errorMessage = '沒有權限讯柔,請聯(lián)系管理員'
    } else if (status === 404) {
      errorMessage = '請求資源不存在'
    } else if (status >= 500) {
      errorMessage = '服務器錯誤抡蛙,請聯(lián)系管理員'
    }
    Message.error(errorMessage)
  } else if (error.request) {
    // 請求發(fā)送成功,未收到響應
    Message.error('請求超時請重試')
  } else {
    // 意料之外的錯誤
    Message.error(error.message)
  }
  // 將本次請求的錯誤對象繼續(xù)向后拋出魂迄,讓接收響應的處理函數(shù)進行操作
  return Promise.reject(error)
})

刷新Token

HTTP 狀態(tài)碼401表示未授權粗截,導致401的情況有:

  • 沒有Token
  • Token無效
  • Token過期
    判斷方法:
    • 檢測是否存在refresh_token:(后端通常會限制每個refresh_token只能獲取一次新的Token)
      • 如果有,那就通過refresh_token獲取新的access_token
        • 獲取成功捣炬,重啟發(fā)送請求熊昌,請求接口數(shù)據(jù)就行
        • 獲取失敗,跳轉登錄頁
      • 如果沒有湿酸,跳轉登錄頁
        由于要進行跳轉婿屹,在utils/request.js中引入router/index.js
// utils/request.js
// 引入 router
import router from '@/router'

首先要檢測store是否有user信息(有就證明是正常登陸,一定存在的有refresh_token)推溃,如果存在的有refresh_token的話就請求新的access_token昂利,需要用到對應的刷新接口,接下來檢查是否有新的access_token

  • 失敗的話美莫,清除用戶信息页眯,跳轉登錄頁
    • 跳轉登錄操作與之前是一致的,建議封裝起來
  • 成功的話厢呵,更新access_token窝撵,同時重新請求之前401的接口
// utils/
...
// 封裝跳轉登錄頁面的函數(shù)
function redirectLogin () {
  router.push({
    name: 'login',
    query: {
      // router.currentRoute 用于獲取當前路由對應的路由信息對象
      redirect: router.currentRoute.fullPath
    }
  })
}

// 設置響應攔截器
request.interceptors.response.use(function (response) {
  ...
}, function (error) {
  // 超出 2xx 都會進入這里
  if (error.response) {
    ...
  } else if (status === 401) {
    if (!store.state.user) {
      /* router.push({
        name: 'login',
        query: {
          // router.currentRoute 用于獲取當前路由對應的路由
          redirect: router.currentRoute.fullPath
        }
      }) */
      // 封裝函數(shù)后更改為調(diào)用
      redirectLogin()
      // 阻止后續(xù)操作,向下拋出錯誤對象
      return Promise.reject(error)
    }
    ...
    }).then(res => {
      if (res.data.state !== 1) {
        // 清除已經(jīng)無效的用戶信息
        store.commit('setUser', null)
        // 跳轉登錄頁
        /* router.push({
          name: 'login',
          query: {
            // router.currentRoute 用于獲取當前路由對應的路由
            redirect: router.currentRoute.fullPath
          }
        }) */
        // 封裝函數(shù)后更改為調(diào)用
        redirectLogin()
        // 阻止后續(xù)操作襟铭,向下拋出錯誤對象
        return Promise.reject(error)
      }
      ...
        }).catch(() => {
        store.commit('setUser', null)
        /* router.push({
          name: 'login',
          query: {
            // router.currentRoute 用于獲取當前路由對應的路由
            redirect: router.currentRoute.fullPath
          }
        }) */
        // 封裝函數(shù)后更改為調(diào)用
        redirectLogin()
        return Promise.reject(error)
      })
  } else if (status === 403) {
  ...

處理Token重復刷新

如果頁面中存在多個請求(大多數(shù)頁面中都不會只有一次請求)碌奉,如果Token過期,每個請求都會刷新Token寒砖,這個時候刷新多次都沒有意義赐劣,又增加了請求個數(shù),還會出現(xiàn)額外的問題


我多次請求用戶信息哩都,就會回到登錄頁面

通過瀏覽器的開發(fā)者工具觀察魁兼,有兩次的刷新Token請求,由于兩次的刷新token攜帶的refresh_token相同漠嵌,會導致一次成功一次失敗咐汞,失敗的那一次會導致頁面跳轉請求頁



為了避免多次請求刷新Token盖呼,可以通過一個變量isRefreshing標記Token的刷新狀態(tài)
  • 默認狀態(tài)為false,并且在發(fā)送刷新Token請求前檢測化撕,狀態(tài)是false才能發(fā)送
  • 發(fā)送刷新請求的時候几晤,設置標記為true
  • 請求完畢,設置為false
// layout/components/app-header.vue
...
// 是否正在更新 Token
let isRefreshing = false

request.interceptors.response.use(function (response) {
...
  } else if (status === 401) {
    if (!store.state.user) {...}
    // 發(fā)送刷新請求前判斷 isRefreshing 是否存在其他已發(fā)送的刷新請求
    // 1 如果有植阴,則將當前請求掛起蟹瘾,等到 Token 刷新完畢再重發(fā),這里先設置為 return
    if (isRefreshing) {
      return
    }
    // 2. 如果沒有掠手,則更新 isRefreshing 并發(fā)送請求憾朴,繼續(xù)執(zhí)行后續(xù)操作
    isRefreshing = true
    // 發(fā)送刷新請求
    return request({
     ...
    }).then(res => {
      ...
    }).catch(() => {
      ...
    }).finally(() => {
      // 3 請求完畢,無論成功失敗惨撇,設置 isRefreshing 為 false
      isRefreshing = false
    })
  } else if (status === 403) {
...

雖然刷新Token的問題解決了伊脓,但是之前發(fā)送的兩個請求只有一個成功執(zhí)行府寒,其他的請求都被阻止了
如何解決魁衙?
我們聲明一個數(shù)組存儲所有被掛起的請求,當Token刷新完畢再將這些請求重新發(fā)送

// 存儲是否正在更新token 的狀態(tài)
let isRefreshing = false
// 存儲因為token刷新而掛起的請求
let requests = []
// 響應攔截器
request.interceptors.response.use(function (response) {
  // 狀態(tài)碼2xx會執(zhí)行這里
  console.log('響應成功了', response)
  return response
}, function (error) {
  if (error.response) {
    // 請求發(fā)送成功株搔,響應接收完畢剖淀,但是狀態(tài)碼為失敗的情況
    // 1.判斷失敗的狀態(tài)碼情況(主要處理401的情況)
    const { status } = error.response
    let errorMessage = ''
    if (status === 400) {
      errorMessage = '請求參數(shù)錯誤'
    } else if (status === 401) {
      // 2.Token無效(過期)處理
      // 第一,無token信息
      if (!store.state.user) {
        redirectLogin()
        return Promise.reject(error)
      }
      // 檢測是否已經(jīng)存在了正在刷新token的請求
      if (isRefreshing) {
        // 將當前失敗的請求存起來纤房,存儲到請求列表中
        return requests.push(() => {
          // 當前函數(shù)調(diào)用后纵隔,會自動發(fā)送本次失敗請求
          request(error.config)
        })
      }
      isRefreshing = true
      // 第二,Token無效(錯誤Token炮姨,過期Token)
      // 發(fā)送請求捌刮,獲取新的access_token
      return request({
        method: 'POST',
        url: '/front/user/refresh_token',
        data: qs.stringify({
          refreshtoken: store.state.user.refresh_token
        })
      }).then(res => {
        // -刷新token失敗
        if (res.data.state !== 1) {
          // 清除無效的用戶信息
          store.commit('setUser', null)
          // 封裝重復的跳轉登錄操作
          redirectLogin()
          return Promise.reject(error)
        }
        // 刷新token成功
        // 存儲新的token
        store.commit('setUser', res.data.content)
        // 重新發(fā)送失敗的請求
        // 根據(jù)reques
        // 發(fā)送多次失敗的請求
        requests.forEach(callback => callback())
        // 發(fā)送完畢清除requests 內(nèi)容即可
        requests = []
        // 將本次請求發(fā)送
        return request(error.config)
      }).catch(err => {
        console.log(err)
      }).finally(() => {
        // 無論成功還是失敗都會執(zhí)行
        // 請求發(fā)送完畢,響應處理完畢舒岸,刷新狀態(tài)更改為false就行了
        isRefreshing = false
      })

解決

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末绅作,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子蛾派,更是在濱河造成了極大的恐慌俄认,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件洪乍,死亡現(xiàn)場離奇詭異眯杏,居然都是意外死亡,警方通過查閱死者的電腦和手機壳澳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進店門岂贩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人巷波,你說我怎么就攤上這事萎津】破剑” “怎么了?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵姜性,是天一觀的道長瞪慧。 經(jīng)常有香客問我,道長部念,這世上最難降的妖魔是什么弃酌? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮儡炼,結果婚禮上妓湘,老公的妹妹穿的比我還像新娘。我一直安慰自己乌询,他們只是感情好榜贴,可當我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著妹田,像睡著了一般唬党。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上鬼佣,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天驶拱,我揣著相機與錄音,去河邊找鬼晶衷。 笑死蓝纲,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的晌纫。 我是一名探鬼主播税迷,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼锹漱!你這毒婦竟也來了箭养?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤凌蔬,失蹤者是張志新(化名)和其女友劉穎露懒,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體砂心,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡懈词,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了辩诞。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片坎弯。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出抠忘,到底是詐尸還是另有隱情撩炊,我是刑警寧澤,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布崎脉,位于F島的核電站拧咳,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏囚灼。R本人自食惡果不足惜骆膝,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望灶体。 院中可真熱鬧阅签,春花似錦、人聲如沸蝎抽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽樟结。三九已至养交,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間狭吼,已是汗流浹背层坠。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工殖妇, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留刁笙,地道東北人。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓谦趣,卻偏偏與公主長得像疲吸,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子前鹅,可洞房花燭夜當晚...
    茶點故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內(nèi)容