第一次接觸token處理,初來乍到涛贯,說錯的地方還請各位多多指教。
token身份驗證機(jī)制
客戶端登錄請求成功后蔚出,服務(wù)器將用戶信息(如用戶id)使用特殊算法加密后作為驗證的標(biāo)志發(fā)送給用戶(即token)弟翘,當(dāng)用戶下次發(fā)起請求時,會將這個token捎帶過來骄酗,服務(wù)器再將這個token通過解密后進(jìn)行驗證稀余,通過的話,則向客戶端返回請求的數(shù)據(jù)趋翻;反之睛琳,則請求失敗。
token優(yōu)點
它是無狀態(tài)的踏烙,且服務(wù)器不用像傳統(tǒng)的身份認(rèn)證(session)那樣需要保存會話信息师骗,減輕了服務(wù)器的壓力。
vue的token刷新處理
在對token身份驗證機(jī)制進(jìn)行一次簡單介紹后讨惩,進(jìn)入正文...
一般為了安全性辟癌,token都會設(shè)置一個過期時間,在過期之后就無法請求相關(guān)接口了荐捻,這時應(yīng)該怎么辦呢黍少,是直接退出登錄嗎?
在目前公司的項目里处面,為了更好的用戶體驗厂置,我們選擇手動刷新token。登錄請求成功后魂角,會返回一個token和token過期時間昵济,在每次請求api時,前端可以先判斷一下token是否即將過期或已過期,如果是砸紊,則請求刷新token的接口传于,成功替換原來的token之后才可以重新發(fā)起請求。
下面醉顽,我們直接看代碼沼溜,這是在vue的請求攔截器里進(jìn)行的相關(guān)操作:
/*是否有請求正在刷新token*/
window.isRefreshing = false
/*被掛起的請求數(shù)組*/
let refreshSubscribers = []
/*獲取刷新token請求的token*/
function getRefreshToken () {
return JSON.parse(localStorage.auth).refresh_token
}
/*push所有請求到數(shù)組中*/
function subscribeTokenRefresh (cb) {
refreshSubscribers.push(cb)
}
/*刷新請求(refreshSubscribers數(shù)組中的請求得到新的token之后會自執(zhí)行,用新的token去請求數(shù)據(jù))*/
function onRrefreshed (token) {
refreshSubscribers.map(cb => cb(token))
}
/*請求攔截器*/
ajax.interceptors.request.use(
config => {
const authTmp = localStorage.auth
/*判斷是否已登錄*/
if (authTmp) {
/*解析登錄信息*/
let auth = JSON.parse(authTmp)
/*判斷auth是否存在*/
if (auth) {
/*在請求頭中添加token類型游添、token*/
config.headers.Authorization = auth.token_type + ' ' + auth.token
/*判斷刷新token請求的refresh_token是否過期*/
if (util.isRefreshTokenExpired()) {
alert('刷新token過期系草,請重新登錄')
/*清除本地保存的auth*/
localStorage.removeItem('auth')
window.location.href = '#/login'
return
}
/*判斷token是否將要過期*/
if (util.isTokenExpired() && config.url.indexOf('admin/auth/current') === -1) {
/*判斷是否正在刷新*/
if (!window.isRefreshing) {
/*將刷新token的標(biāo)志置為true*/
window.isRefreshing = true
/*發(fā)起刷新token的請求*/
apiList.refreshToken({refresh_token: getRefreshToken()}).then(res => {
/*將標(biāo)志置為false*/
window.isRefreshing = false
/*成功刷新token*/
config.headers.Authorization = res.data.data.token_type + ' ' + res.data.data.token
/*更新auth*/
localStorage.setItem('auth', JSON.stringify(res.data.data))
/*執(zhí)行數(shù)組里的函數(shù),重新發(fā)起被掛起的請求*/
onRrefreshed(res.data.data.token)
/*執(zhí)行onRefreshed函數(shù)后清空數(shù)組中保存的請求*/
refreshSubscribers = []
}).catch(err => {
alert(err.response.data.message)
/*清除本地保存的auth*/
// localStorage.removeItem('auth')
window.location.href = '#/login'
})
}
/*把請求(token)=>{....}都push到一個數(shù)組中*/
let retry = new Promise((resolve, reject) => {
/*(token) => {...}這個函數(shù)就是回調(diào)函數(shù)*/
subscribeTokenRefresh((token) => {
config.headers.Authorization = 'Bearer ' + token
/*將請求掛起*/
resolve(config)
})
})
return retry
}
}
return config
} else {
/*未登錄直接返回配置信息*/
return config
}
},
/*錯誤操作*/
err => {
return Promise.reject(err)
}
)
這里需要注意幾點:
1、當(dāng)token即將過期或者已過期時唆涝,原則上找都,我們只需要有一個接口去觸發(fā)刷新token的請求即可,這里的isRefreshing 變量廊酣,就起到這樣一個監(jiān)控的作用能耻,它相當(dāng)于一把鎖,當(dāng)刷新token的操作被觸發(fā)后亡驰,其他的觸發(fā)操作就被排斥在外了晓猛。
window.isRefreshing = false
2、刷新token的接口凡辱,用到了一個另外的token(refresh_token)戒职,這也是出于安全性考慮的,并且它也有過期時間透乾,不過這個過期時間一般都比普通token的過期時間要長洪燥,所以在上面代碼中,會發(fā)現(xiàn)乳乌,我在請求攔截中優(yōu)先判斷了refresh_token是否過期捧韵,如果過期則直接退出登錄,不再進(jìn)行下一步的操作钦扭。
/*判斷刷新token請求的refresh_token是否過期*/
if (util.isRefreshTokenExpired() && config.url.indexOf('admin/auth/current') === -1) {
alert('刷新token過期纫版,請重新登錄')
/*清除本地保存的auth*/
localStorage.removeItem('auth')
window.location.href = '#/login'
return
}
3、在觸發(fā)了刷新token的操作后客情,我們還需要先將其他的請求掛起其弊,在獲取新的token之后再重新發(fā)起這些請求。
/*把請求(token)=>{....}都push到一個數(shù)組中*/
let retry = new Promise((resolve, reject) => {
/*(token) => {...}這個函數(shù)就是回調(diào)函數(shù)*/
subscribeTokenRefresh((token) => {
config.headers.Authorization = 'Bearer ' + token
/*將請求掛起*/
resolve(config)
})
})
return retry
在刷新token請求的成功回調(diào)里執(zhí)行下面代碼膀斋,重新發(fā)起請求梭伐。
/*執(zhí)行數(shù)組里的函數(shù),重新發(fā)起被掛起的請求*/
onRrefreshed(res.data.data.token)
4、因為有人在評論里問util文件仰担,應(yīng)該是想知道具體怎么判斷token過期的糊识,其實在獲得token時,是有返回一個token過期時間 ,你可以先將它先保存起來赂苗,然后在需要時愉耙,拿出來與本地時間比較即可
/*判斷token是否過期*/
function isTokenExpired() {
/*從localStorage中取出token過期時間*/
let expiredTime = new Date(JSON.parse(localStorage.auth).expired_at).getTime() / 1000
/*獲取本地時間*/
let nowTime = new Date().getTime() / 1000
/*獲取校驗時間差*/
let diffTime = JSON.parse(sessionStorage.diffTime)
/*校驗本地時間*/
nowTime -= diffTime
/*如果 < 10分鐘,則說明即將過期*/
return (expiredTime - nowTime) < 10*60
}