欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

前端無感知刷新token以及超時(shí)自動(dòng)退出實(shí)現(xiàn)方案

 更新時(shí)間:2024年01月09日 11:01:00   作者:ww_怒放  
前端需要做到無感刷新token,即刷token時(shí)要做到用戶無感知,避免頻繁登錄,下面這篇文章主要給大家介紹了關(guān)于前端無感知刷新token以及超時(shí)自動(dòng)退出的實(shí)現(xiàn)方案,需要的朋友可以參考下

一、token的作用

因?yàn)閔ttp請(qǐng)求是無狀態(tài)的,是一次性的,請(qǐng)求之間沒有任何關(guān)系,服務(wù)端無法知道請(qǐng)求者的身份,所以需要鑒權(quán),來驗(yàn)證當(dāng)前用戶是否有訪問系統(tǒng)的權(quán)限。

以oauth2.0授權(quán)碼模式為例:

每次請(qǐng)求資源服務(wù)器時(shí)都會(huì)在請(qǐng)求頭中添加 Authorization: Bearer access_token 資源服務(wù)器會(huì)先判斷token是否有效,如果無效或過期則響應(yīng) 401 Unauthorize。此時(shí)用戶處于操作狀態(tài),應(yīng)該自動(dòng)刷新token保證用戶的行為正常進(jìn)行。

刷新token:使用refresh_token獲取新的access_token,使用新的access_token重新發(fā)起失敗的請(qǐng)求。

二、無感知刷新token方案

2.1 刷新方案

當(dāng)請(qǐng)求出現(xiàn)狀態(tài)碼為 401 時(shí)表明token失效或過期,攔截響應(yīng),刷新token,使用新的token重新發(fā)起該請(qǐng)求。

如果刷新token的過程中,還有其他的請(qǐng)求,則應(yīng)該將其他請(qǐng)求也保存下來,等token刷新完成,按順序重新發(fā)起所有請(qǐng)求。

2.2 原生AJAX請(qǐng)求

2.2.1 http工廠函數(shù)

function httpFactory({ method, url, body, headers, readAs, timeout }) {
    const xhr = new XMLHttpRequest()
    xhr.open(method, url)
    xhr.timeout = isNumber(timeout) ? timeout : 1000 * 60
?
    if(headers){
        forEach(headers, (value, name) => value && xhr.setRequestHeader(name, value))
    }
    
    const HTTPPromise = new Promise((resolve, reject) => {
        xhr.onload = function () {
            let response;
?
            if (readAs === 'json') {
                try {
                    response = JSONbig.parse(this.responseText || null);
                } catch {
                    response = this.responseText || null;
                }
            } else if (readAs === 'xml') {
                response = this.responseXML
            } else {
                response = this.responseText
            }
?
            resolve({ status: xhr.status, response, getResponseHeader: (name) => xhr.getResponseHeader(name) })
        }
?
        xhr.onerror = function () {
            reject(xhr)
        }
        xhr.ontimeout = function () {
            reject({ ...xhr, isTimeout: true })
        }
?
        beforeSend(xhr)
?
        body ? xhr.send(body) : xhr.send()
?
        xhr.onreadystatechange = function () {
            if (xhr.status === 502) {
                reject(xhr)
            }
        }
    })
?
    // 允許HTTP請(qǐng)求中斷
    HTTPPromise.abort = () => xhr.abort()
?
    return HTTPPromise;
}

2.2.2 無感知刷新token

// 是否正在刷新token的標(biāo)記
let isRefreshing = false
?
// 存放因token過期而失敗的請(qǐng)求
let requests = []
?
function httpRequest(config) {
    let abort
    let process = new Promise(async (resolve, reject) => {
        const request = httpFactory({...config, headers: { Authorization: 'Bearer ' + cookie.load('access_token'), ...configs.headers }})
        abort = request.abort
        
        try {                             
            const { status, response, getResponseHeader } = await request
?
            if(status === 401) {
                try {
                    if (!isRefreshing) {
                        isRefreshing = true
                        
                        // 刷新token
                        await refreshToken()
?
                        // 按順序重新發(fā)起所有失敗的請(qǐng)求
                        const allRequests = [() => resolve(httpRequest(config)), ...requests]
                        allRequests.forEach((cb) => cb())
                    } else {
                        // 正在刷新token,將請(qǐng)求暫存
                        requests = [
                            ...requests,
                            () => resolve(httpRequest(config)),
                        ]
                    }
                } catch(err) {
                    reject(err)
                } finally {
                    isRefreshing = false
                    requests = []
                }
            }                        
        } catch(ex) {
            reject(ex)
        }
    })
    
    process.abort = abort
    return process
}
?
// 發(fā)起請(qǐng)求
httpRequest({ method: 'get', url: 'http://127.0.0.1:8000/api/v1/getlist' })

2.3 Axios 無感知刷新token

// 是否正在刷新token的標(biāo)記
let isRefreshing = false
?
let requests: ReadonlyArray<(config: any) => void> = []
?
// 錯(cuò)誤響應(yīng)攔截
axiosInstance.interceptors.response.use((res) => res, async (err) => {
    if (err.response && err.response.status === 401) {
        try {
            if (!isRefreshing) {
                isRefreshing = true
                // 刷新token
                const { access_token } = await refreshToken()
?
                if (access_token) {
                    axiosInstance.defaults.headers.common.Authorization = `Bearer ${access_token}`;
?
                    requests.forEach((cb) => cb(access_token))
                    requests = []
?
                    return axiosInstance.request({
                        ...err.config,
                        headers: {
                            ...(err.config.headers || {}),
                            Authorization: `Bearer ${access_token}`,
                        },
                    })
                }
?
                throw err
            }
?
            return new Promise((resolve) => {
                // 將resolve放進(jìn)隊(duì)列,用一個(gè)函數(shù)形式來保存,等token刷新后直接執(zhí)行
                requests = [
                    ...requests,
                    (token) => resolve(axiosInstance.request({
                        ...err.config,
                        headers: {
                            ...(err.config.headers || {}),
                            Authorization: `Bearer ${token}`,
                        },
                    })),
                ]
            })
        } catch (e) {
            isRefreshing = false
            throw err
        } finally {
            if (!requests.length) {
                isRefreshing = false
            }
        }
    } else {
        throw err
    }
})

三、長(zhǎng)時(shí)間無操作超時(shí)自動(dòng)退出

當(dāng)用戶登錄之后,長(zhǎng)時(shí)間不操作應(yīng)該做自動(dòng)退出功能,提高用戶數(shù)據(jù)的安全性。

3.1 操作事件

操作事件:用戶操作事件主要包含鼠標(biāo)點(diǎn)擊、移動(dòng)、滾動(dòng)事件和鍵盤事件等。

特殊事件:某些耗時(shí)的功能,比如上傳、下載等。

3.2 方案

用戶在登錄頁面之后,可以復(fù)制成多個(gè)標(biāo)簽,在某一個(gè)標(biāo)簽有操作,其他標(biāo)簽也不應(yīng)該自動(dòng)退出。所以需要標(biāo)簽頁之間共享操作信息。這里我們使用 localStorage 來實(shí)現(xiàn)跨標(biāo)簽頁共享數(shù)據(jù)。

在 localStorage 存入兩個(gè)字段:

名稱類型說明說明
lastActiveTimestring最后一次觸發(fā)操作事件的時(shí)間戳
activeEventsstring[ ]特殊事件名稱數(shù)組

當(dāng)有操作事件時(shí),將當(dāng)前時(shí)間戳存入 lastActiveTime。

當(dāng)有特殊事件時(shí),將特殊事件名稱存入 activeEvents ,等特殊事件結(jié)束后,將該事件移除。

設(shè)置定時(shí)器,每1分鐘獲取一次 localStorage 這兩個(gè)字段,優(yōu)先判斷 activeEvents 是否為空,若不為空則更新 lastActiveTime 為當(dāng)前時(shí)間,若為空,則使用當(dāng)前時(shí)間減去 lastActiveTime 得到的值與規(guī)定值(假設(shè)為1h)做比較,大于 1h 則退出登錄。

3.3 代碼實(shí)現(xiàn)

const LastTimeKey = 'lastActiveTime'
const activeEventsKey = 'activeEvents'
const debounceWaitTime = 2 * 1000
const IntervalTimeOut = 1 * 60 * 1000
?
export const updateActivityStatus = debounce(() => {
    localStorage.set(LastTimeKey, new Date().getTime())
}, debounceWaitTime)
?
/**
 * 頁面超時(shí)未有操作事件退出登錄
 */
export function timeout(keepTime = 60) {
    document.addEventListener('mousedown', updateActivityStatus)
    document.addEventListener('mouseover', updateActivityStatus)
    document.addEventListener('wheel', updateActivityStatus)
    document.addEventListener('keydown', updateActivityStatus)
?
    // 定時(shí)器
    let timer;
?
    const doTimeout = () => {
        timer && clearTimeout(timer)
        localStorage.remove(LastTimeKey)
        document.removeEventListener('mousedown', updateActivityStatus)
        document.removeEventListener('mouseover', updateActivityStatus)
        document.removeEventListener('wheel', updateActivityStatus)
        document.removeEventListener('keydown', updateActivityStatus)
?
        // 注銷token,清空session,回到登錄頁
        logout()
    }
?
    /**
     * 重置定時(shí)器
     */
    function resetTimer() {
        localStorage.set(LastTimeKey, new Date().getTime())
?
        if (timer) {
            clearInterval(timer)
        }
?
        timer = setInterval(() => {
            const isSignin = document.cookie.includes('access_token')
            if (!isSignin) {
                doTimeout()
                return
            }
?
            const activeEvents = localStorage.get(activeEventsKey)
            if(!isEmpty(activeEvents)) {
                localStorage.set(LastTimeKey, new Date().getTime())
                return
            }
            
            const lastTime = Number(localStorage.get(LastTimeKey))
?
            if (!lastTime || Number.isNaN(lastTime)) {
                localStorage.set(LastTimeKey, new Date().getTime())
                return
            }
?
            const now = new Date().getTime()
            const time = now - lastTime
?
            if (time >= keepTime) {
                doTimeout()
            }
        }, IntervalTimeOut)
    }
?
    resetTimer()
}
?
// 上傳操作
function upload() {
    const current = JSON.parse(localStorage.get(activeEventsKey))
    localStorage.set(activeEventsKey, [...current, 'upload'])
    ...
    // do upload request
    ...
    const current = JSON.parse(localStorage.get(activeEventsKey))
    localStorage.set(activeEventsKey, Array.isArray(current) ? current.filter((item) => itme !== 'upload'))
}

總結(jié) 

到此這篇關(guān)于前端無感知刷新token以及超時(shí)自動(dòng)退出實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)前端無感知刷新token內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論