Axios 全域設定、攔截器與 JWT 換發


Posted by mumu892101 on 2022-08-14

專案一開始是使用 fetch 來進行 AJAX 請求,後來有上傳圖片時希望能顯示上傳進度的進度條需求,就 survey 了一下,發現 axios progress 事件取得上傳進度,就決定把專案請求 AJAX 的方式更換成 axios。

不過這篇文章要記錄的,不是後來被取消的進度條功能,而是決定使用 axios 之後,查看 axios npm 文件 後的 config 設定和 Interceptors 攔截器。

之前在使用 fetch 時,會寫一個 hook 來將共用的設定打包,再將 http method 作為參數傳入。axios 的官方文件中則直接提供了 config 的設定,另外也可以設定全局的 defaults 設定。

引入 axios 及做 default 設定

首先先引入 axios :

import axiox from 'axios'

接著可以進行一些 default 的設定。例如 axios.defaults.withCredentials = true,讓跨域的請求可以在發出時正常帶上 cookie。

創建 axios request
const axiosRequest = axios.create({
  baseURL: BASE_URL, // API_URL
  headers: {
    Accept: 'application/json',
    'Content-Type': 'application/json'
  }
})

這個是在這個專案中會用到固定 config,但登入相關的 request 可以不需要 Authorization,登入之後的則都需要帶上 身份 token,因此我們接下來則需要再 request 發出前判斷是不是已經在 redux 中已經存在 accessToken,如果是則要在headers 加入。

request interceptor

上面說的 request 發出前所做的事情,就是中間攔截器的用途。而攔截器除了在 request 發出前使用,也可以用於 收到 response 之後進行行為的統一管理。

首先在專案裡,用 redux 來管理使用者登入後的 Token 資料。這邊不需要根據是否加上 Authorization header 創建兩個不同的 axios 請求,而是在送出 request 之前判斷使否能在 redux store 中取得登入後儲存的 accessToken,如果可以的話代表已經登入,而所有登入後的 AJAX 請求都要加上 Authorization header 才能成功執行。

axiosRequest.interceptors.request.use(
  (config) => {
    const { accessToken } = getState().user // redux user store
    if (accessToken) {
      // 如果有token就攜帶在headers中
      config.headers.Authorization = `Bearer ${accessToken}`
    }
    return config // return config axiosRequest 
  },
  (error) => {
    Promise.reject(error)
  }
)
response interceptor 與 JWT 換發

接收到 response 之後,我們可以對指定的 response status 做統一錯誤管理,例如 status >= 500 的部分,我們可以統一導向一個 error page 優化使用者體驗,讓用戶知道目前可能有一些錯誤問題,而非頁面直接停住。
專案則是對 JWT 換發有一些處理,前後端統一出一組錯誤代碼為 token失效 ,當 errorData.Code === '990403002' 時,都是指涉這個 token 錯誤問題,因此當我們的失敗的 request url 並非換發 token 的 url 而且收到指定錯誤代碼時,則執行更新 token 的 AJAX 請求。當換發成功時,我們將一開始出現錯誤的請求 Authorization header 更換成新的 token 並 return AJAX 請求執行 ,排除這個 token 失效造成的錯誤。

plantsistRequest.interceptors.response.use(
  (res) => {
    return res
  },
  (err) => {
    if (err.response) {
      const errorData = err.response.data
      const originalRequest = err.config
      switch (err.response.status) {
        case 401:
          if (
            err.config.url !== refreshAPIurl &&
            errorData.Code === '990403002'
          ) {
            return plantsistRequest
              .get(refreshAPIurl)
              .then((res) => {
                if (res.data.code === '200') {
                  const resData = res.data.data
                  dispatch(saveUserToken(resData))
                  originalRequest.headers.Authorization = `Bearer ${resData.accessToken}`
                  return axios(originalRequest)
                }
              })
              .catch((err) => {
                return Promise.reject(err)
              })
          }
          if (
            err.config.url !== refreshAPIurl &&
            errorData.code === '990403003'
          ) {
            ... // token 不合法直接登出
          }
          if (err.config.url === refreshAPIurl) {
            ... // 換發失敗直接登出
          }
          break
        default:
          return Promise.reject(err)
      }
    }
  }
)

#Axios







Related Posts

邏輯論證(Logical Argument)

邏輯論證(Logical Argument)

簡明 Vim 文字編輯器操作入門教學

簡明 Vim 文字編輯器操作入門教學

安裝 Git/ Git 的基本指令

安裝 Git/ Git 的基本指令


Comments