1. 程式人生 > >基於OAuth2.0的token無感知重新整理

基於OAuth2.0的token無感知重新整理

  目前手頭的vue專案關於許可權一塊有一個需求,其實架構師很早就要求我做了,但是由於這個緊急程度不是很高,最近臨近專案上線,我才想起,於是趕緊補上這個功能。這個專案是基於OAuth2.0認證,需要在每個請求的頭部攜帶access_token,如果這個access_token過期,需要利用已有的refresh _token去重新獲取一個access_token,如果連這個refresh_token也過期了,那就是真正的過期了,需要退出登入頁面。refresh_token在獲取新的access_token的時候需要讓使用者無感知,也叫無痛重新整理。

  這裡的程式碼實現肯定是要在axios攔截器裡寫的,但是是在請求攔截器裡寫還是在響應攔截器裡寫還是有區別的:

  1.寫在請求攔截器裡:每次請求之前都會先請求一個checkToken的介面,來確認這個access_token是否過期,如果沒有過期,直接就發起原本的請求,如果過期,利用已有的refresh _token去重新獲取一個access_token之後,再發起原本的請求。但是這樣寫有個缺點,就是每次請求之前都要額外請求一次checkToken的介面,如果網速不好,會給使用者造成不好的體驗,而且對伺服器造成了效能上的浪費。

  2.寫在響應攔截器裡:直到access_token過期,返回401未授權,才利用已有的refresh _token去重新獲取一個access_token。

  最後我和後端討論了下,最後採用了第二種方法,把checkToken放在後端,前端無感知重新整理寫在響應攔截器裡。

這裡寫的一個響應攔截器:

import axios from 'axios'

//建立一個axios例項
const service = axios.create({
  timeout: 5000, // 請求超時時間
  withCredentials:true //表示跨域請求時是否需要使用憑證. 預設為false
})
var loading;//遮罩層

// 響應攔截器
service.interceptors.response.use( response => { //do what you like }, error => { loading.close(); if (error && error.response) { switch (error.response.status) { case 400: error.message = '請求錯誤' break case 401: return doRequest(error); case 403: error.message = '拒絕訪問' break case 404: error.message = `請求地址出錯: ${error.response.config.url}` break case 408: error.message = '請求超時' break case 500: error.message = '伺服器內部錯誤' break case 501: error.message = '服務未實現' break case 502: error.message = '閘道器錯誤' break case 503: error.message = '服務不可用' break case 504: error.message = '閘道器超時' break case 505: error.message = 'HTTP版本不受支援' break default: break } } errorLog(error) return Promise.reject(error) } )

  export default service
 

  可以看到在響應攔截器的錯誤回撥函式裡401值的時候呼叫了一個方法doRequest();

async function doRequest (error) {
  try {
    const data = await getNewToken();
    var token=data.data.token_type+' '+data.data.access_token;
    sessionStorage.setItem('RequestToken',token);
    const res = await service.request(error.config)
    return res;
  } catch(err) {
    Message({
      message: '登入會話已過期,請重新登入',
      type: 'error',
      duration: 5 * 1000
    })
    sessionStorage.clear();
    router.replace({
      path:"/login"
    });
    return err;
  }
}

  這裡的重點這些請求必須是同步的,同步的,同步的,重要的事情說三遍,而axios預設是非同步的,所以你要麼使用ES6的async/await語句,要麼使用then回撥函式,必須保持是同步的。而getNewToken()則是利用refresh_token重新獲取access_token方法。算了,一併貼出,僅作參考。

import qs from 'qs'

async function getNewToken() {
  var refreshToken=sessionStorage.getItem('refreshToken');
  return await axios({
    url: '/OAuth/oauth/token',
      method: 'post',
      headers: {
        'Authorization': 'Basic XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
        'Content-Type':'application/x-www-form-urlencoded'
      },
    data:qs.stringify({
      grant_type:'refresh_token',
      refresh_token : refreshToken
    })
  })
}

  下面看效果。為了效果,這裡設定了access_token有效時間為5s,refresh _token有效時間為10s。動圖是這樣的:

  一步一步分解下,登入的時候,獲取到access_token和refresh _token。然後帶著access_token:f0a3******cb64去訪問menuQuery介面是可以正常請求的。

   但是之後,我等了超過5s後(不超過10s,這個時候access_token已過期,refresh _token未過期)發了一個對0304介面的請求,這個時候返回401未授權,說明access_token:f0a3******cb64已過期。

   這時利用refresh_token重新獲取access_token。

  可以看到返回了一個新的access_token:8332******1c8a,於是帶著這個新的access_token重新發起對0304介面的請求,這個時候就可以返回所需要的資料。

 

 

  之後再等超過5s,這個時候access_token過期了,refresh _token也過期了。動圖是這樣的:

 

   這時的請求返回的是400,而不是401了,這說明refresh _token:826b******17d1過期了。這個時候就該退出登入介面,重新登入了。

   最後,放一個總的效果圖: