宅男在线永久免费观看网直播,亚洲欧洲日产国码无码久久99,野花社区在线观看视频,亚洲人交乣女bbw,一本一本久久a久久精品综合不卡

全部
常見(jiàn)問(wèn)題
產(chǎn)品動(dòng)態(tài)
精選推薦

前端自動(dòng)刷新Token與超時(shí)安全退出攻略

管理 管理 編輯 刪除

一、token的作用

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

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

89e4a202403071133361293.png


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

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

二、無(wú)感知刷新token方案

2.1 刷新方案

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

如果刷新token的過(guò)程中,還有其他的請(qǐng)求,則應(yīng)該將其他請(qǐng)求也保存下來(lái),等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 無(wú)感知刷新token

// 是否正在刷新token的標(biāo)記
let isRefreshing = false
?
// 存放因token過(guò)期而失敗的請(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 無(wú)感知刷新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ù)形式來(lái)保存,等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í)間無(wú)操作超時(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 方案

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

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

名稱類型說(shuō)明說(shuō)明
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)
?
/**
 * 頁(yè)面超時(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,回到登錄頁(yè)
 ? ? ? ?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'))
}


請(qǐng)登錄后查看

CRMEB-慕白寒窗雪 最后編輯于2024-03-07 11:35:22

快捷回復(fù)
回復(fù)
回復(fù)
回復(fù)({{post_count}}) {{!is_user ? '我的回復(fù)' :'全部回復(fù)'}}
排序 默認(rèn)正序 回復(fù)倒序 點(diǎn)贊倒序

{{item.user_info.nickname ? item.user_info.nickname : item.user_name}} LV.{{ item.user_info.bbs_level || item.bbs_level }}

作者 管理員 企業(yè)

{{item.floor}}# 同步到gitee 已同步到gitee {{item.is_suggest == 1? '取消推薦': '推薦'}}
{{item.is_suggest == 1? '取消推薦': '推薦'}}
沙發(fā) 板凳 地板 {{item.floor}}#
{{item.user_info.title || '暫無(wú)簡(jiǎn)介'}}
附件

{{itemf.name}}

{{item.created_at}}  {{item.ip_address}}
打賞
已打賞¥{{item.reward_price}}
{{item.like_count}}
{{item.showReply ? '取消回復(fù)' : '回復(fù)'}}
刪除
回復(fù)
回復(fù)

{{itemc.user_info.nickname}}

{{itemc.user_name}}

回復(fù) {{itemc.comment_user_info.nickname}}

附件

{{itemf.name}}

{{itemc.created_at}}
打賞
已打賞¥{{itemc.reward_price}}
{{itemc.like_count}}
{{itemc.showReply ? '取消回復(fù)' : '回復(fù)'}}
刪除
回復(fù)
回復(fù)
查看更多
打賞
已打賞¥{{reward_price}}
3494
{{like_count}}
{{collect_count}}
添加回復(fù) ({{post_count}})

相關(guān)推薦

快速安全登錄

使用微信掃碼登錄
{{item.label}} 加精
{{item.label}} {{item.label}} 板塊推薦 常見(jiàn)問(wèn)題 產(chǎn)品動(dòng)態(tài) 精選推薦 首頁(yè)頭條 首頁(yè)動(dòng)態(tài) 首頁(yè)推薦
取 消 確 定
回復(fù)
回復(fù)
問(wèn)題:
問(wèn)題自動(dòng)獲取的帖子內(nèi)容,不準(zhǔn)確時(shí)需要手動(dòng)修改. [獲取答案]
答案:
提交
bug 需求 取 消 確 定
打賞金額
當(dāng)前余額:¥{{rewardUserInfo.reward_price}}
{{item.price}}元
請(qǐng)輸入 0.1-{{reward_max_price}} 范圍內(nèi)的數(shù)值
打賞成功
¥{{price}}
完成 確認(rèn)打賞

微信登錄/注冊(cè)

切換手機(jī)號(hào)登錄

{{ bind_phone ? '綁定手機(jī)' : '手機(jī)登錄'}}

{{codeText}}
切換微信登錄/注冊(cè)
暫不綁定
CRMEB客服

CRMEB咨詢熱線 咨詢熱線

400-8888-794

微信掃碼咨詢

CRMEB開(kāi)源商城下載 源碼下載 CRMEB幫助文檔 幫助文檔
返回頂部 返回頂部
CRMEB客服