前端實(shí)現(xiàn) refresh_token刷新, 無(wú)痛token刷新機(jī)制

前言

目前登錄有很多用的是token機(jī)制, 因?yàn)榘踩詥?wèn)題, 一般都會(huì)返回一個(gè)刷新token 和 使用的token, 還有過(guò)期時(shí)間, 可以根據(jù)過(guò)期時(shí)間, 當(dāng)現(xiàn)有token失效時(shí), 重新獲取新的token

token機(jī)制.png

當(dāng)token失效或即將失效時(shí), 重新獲取新的token, 但因?yàn)閍jax是異步的, 請(qǐng)求新的token是需要一定時(shí)間, 此時(shí)若是有新的請(qǐng)求接口, 就會(huì)出現(xiàn)問(wèn)題

在網(wǎng)上查詢資料后, 一般有兩種方法可以實(shí)現(xiàn)無(wú)痛刷新token機(jī)制:

  • 在請(qǐng)求攔截中, 攔截刷新token時(shí), 將額外接口緩存, 刷新成功后在重新發(fā)送請(qǐng)求
  • 在響應(yīng)攔截中, 將額外接口緩存, 刷新成功后在重新發(fā)送請(qǐng)求

因?yàn)橐婚_(kāi)始找到的方法就是第二種, 所以直接使用第二種方法

此方法有一個(gè)缺點(diǎn): 就是緩存接口會(huì)請(qǐng)求二次, 消耗性能

解決思路

基于axios的vue框架

大致思路:

  • 判斷token是否過(guò)期 以及 是否即將過(guò)期
  • 過(guò)期后, 發(fā)送刷新token請(qǐng)求, 并將刷新token期間請(qǐng)求的接口緩存起來(lái)
  • 請(qǐng)求成功后, 在重新請(qǐng)求之前的請(qǐng)求

方案

// 創(chuàng)建axios實(shí)例
const service = axios.create({
    withCredentials: false, // 請(qǐng)求不帶cookie
    baseURL: baseURL, // api 的 base_url
    timeout: timeout // 請(qǐng)求超時(shí)時(shí)間
});


// 請(qǐng)求攔截器沒(méi)有做處理
service.interceptors.request.use(config => {
    ...
    return config
})

// 是否正在刷新的標(biāo)記 -- 防止重復(fù)發(fā)出刷新token接口
let isRefreshing = false;
// 判斷token是否失效: return: true為過(guò)期
function isOAverdue() {
    // 當(dāng)離過(guò)期時(shí)間還有半小時(shí)時(shí), 也判斷為過(guò)期
    // getTokenItem('time'): 獲取存入localStorage的過(guò)期時(shí)間
    return Math.floor((Date.now() - getTokenItme('time')) / 1000) + 30 * 60 > 
        getTokenItme('expires_in');
}
// 失效后同時(shí)發(fā)送請(qǐng)求的容器 -- 緩存接口
let subscribers = [];
// 刷新 token 后, 將緩存的接口重新請(qǐng)求一次
function onAccessTokenFetched(newToken) {
    subscribers.forEach((callback) => {
        callback(newToken);
    });
    // 清空緩存接口
    subscribers = [];
}
// 添加緩存接口
function addSubscriber(callback) {
    subscribers.push(callback);
}

// 響應(yīng)攔截器
service.interceptors.response.use(
    response => {
        // 當(dāng)response.data.re為401, 則判斷token已經(jīng)過(guò)期
        // /oauth/token為刷新token的接口, 需要排除掉 
        if ((isOAverdue() || response.data.ret === 401) && !response.config.url.includes('/oauth/token')) {
            if (!isRefreshing) {
                isRefreshing = true;
                // 將刷新token的方法放在vuex中處理了, 可見(jiàn)下面區(qū)塊代碼
                store.dispatch('refreshToken').then((res) => {
                    // 當(dāng)刷新成功后, 重新發(fā)送緩存請(qǐng)求
                    onAccessTokenFetched(res);
                }).catch(() => {
                    // 刷新token報(bào)錯(cuò)的話, 就需要跳轉(zhuǎn)到登錄頁(yè)面
                    window.location = '/#/guide/login';
                }).finally(() => {
                    isRefreshing = false;
                });
            }
            // 將其他接口緩存起來(lái) -- 這個(gè)Promise函數(shù)很關(guān)鍵
            const retryOriginalRequest = new Promise((resolve) => {
                // 這里是將其他接口緩存起來(lái)的關(guān)鍵, 返回Promise并且讓其狀態(tài)一直為等待狀態(tài), 
                // 只有當(dāng)token刷新成功后, 就會(huì)調(diào)用通過(guò)addSubscriber函數(shù)添加的緩存接口, 
                // 此時(shí), Promise的狀態(tài)就會(huì)變成resolve
                addSubscriber((newToken) => {
                    // 表示用新的token去替換掉原來(lái)的token
                    response.config.headers.Authorization = `bearer ${newToken}`;
                    // 替換掉url -- 因?yàn)閎aseURL會(huì)擴(kuò)展請(qǐng)求url
                    response.config.url = response.config.url.replace(response.config.baseURL, '');
                    // 用重新封裝的config去請(qǐng)求, 就會(huì)將重新請(qǐng)求后的返回
                    resolve(service(response.config));
                });
            });
            return retryOriginalRequest;
        }
        // ========================
        
        // .... 省略其他代碼.....
    }
    
    // vuex中刷新token方法
    async refreshToken({ commit }) {
        // getTokenItem('refresh_token): 獲取刷新token
        try {
            let res = await refreshToken(getTokenItem('refresh_token));
            const data = res.access_token;
            newSetToken(res);
            return data;
         } catch(e) {
            return '刷新token出錯(cuò)';
         }
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容