目前手頭的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) } )
能夠看到在響應攔截器的錯誤回調函數裏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過時了。這個時候就該退出登陸界面,從新登陸了。
最後,放一個總的效果圖: