基於OAuth2.0的token無感知刷新

  目前手頭的vue項目關於權限一塊有一個需求,其實架構師很早就要求我作了,可是因爲這個緊急程度不是很高,最近臨近項目上線,我纔想起,因而趕忙補上這個功能。這個項目是基於OAuth2.0認證,須要在每一個請求的頭部攜帶access_token,若是這個access_token過時,須要利用已有的refresh _token去從新獲取一個access_token,若是連這個refresh_token也過時了,那就是真正的過時了,須要退出登陸頁面。refresh_token在獲取新的access_token的時候須要讓用戶無感知,也叫無痛刷新。前端

  這裏的代碼實現確定是要在axios攔截器裏寫的,可是是在請求攔截器裏寫仍是在響應攔截器裏寫仍是有區別的:vue

  1.寫在請求攔截器裏:每次請求以前都會先請求一個checkToken的接口,來確認這個access_token是否過時,若是沒有過時,直接就發起本來的請求,若是過時,利用已有的refresh _token去從新獲取一個access_token以後,再發起本來的請求。可是這樣寫有個缺點,就是每次請求以前都要額外請求一次checkToken的接口,若是網速很差,會給用戶形成很差的體驗,並且對服務器形成了性能上的浪費。ios

  2.寫在響應攔截器裏:直到access_token過時,返回401未受權,才利用已有的refresh _token去從新獲取一個access_token。axios

  最後我和後端討論了下,最後採用了第二種方法,把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方法。算了,一併貼出,僅做參考。session

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接口是能夠正常請求的。app

   可是以後,我等了超過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過時了。這個時候就該退出登陸界面,從新登陸了。

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

相關文章
相關標籤/搜索