您好,登錄后才能下訂單哦!
這期內(nèi)容當(dāng)中小編將會(huì)給大家?guī)碛嘘P(guān)token 機(jī)制的實(shí)現(xiàn)原理是什么,文章內(nèi)容豐富且以專業(yè)的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。
token 直譯就是令牌的意思,其實(shí)就是后端將用戶信息進(jìn)行非對(duì)稱加密,然后將加密后的內(nèi)容保存在前端,當(dāng)發(fā)送請(qǐng)求的時(shí)候帶上這個(gè)令牌來實(shí)現(xiàn)身份驗(yàn)證。大致的過程是第一次登錄用戶輸入用戶名和密碼,服務(wù)器驗(yàn)證無誤后會(huì)對(duì)用戶的信息進(jìn)行非對(duì)稱加密生成一個(gè)令牌返回給前端,前端可以存入 cookie 或者 localStorage 等,以后每次發(fā)送請(qǐng)求帶上這個(gè)令牌,后端通過對(duì)令牌的驗(yàn)證來識(shí)別用戶的身份以及請(qǐng)求的合法性。
token 的優(yōu)點(diǎn)是服務(wù)端不需要保存 token,只需要驗(yàn)證前端傳過來的 token 即可,所以幾遍是分布式部署也可以使用這種方式。token 的缺點(diǎn)就是,由于服務(wù)器不保存 session 狀態(tài),因此無法在使用過程中廢止某個(gè) token,或者更改 token 的權(quán)限。也就是說,一旦 token 簽發(fā)了,在到期之前就會(huì)始終有效,除非服務(wù)器部署額外的邏輯。
目前比較常用的 token 加密方式是 JWT JSON Web Token,關(guān)于 JWT 可以參考阮一峰老師的 JSON Web Token 入門教程
按照上面的 token 邏輯,前端只要保存一個(gè)后端傳過來的 token,每次請(qǐng)求附上即可。當(dāng)令牌過期有兩種選擇,我們可以讓用戶沖洗你登錄,或者后端生成一個(gè)新的令牌,前端保存新的令牌并重新發(fā)送請(qǐng)求。但是這兩種方式都有問題,如果讓用戶重新登錄,用戶體驗(yàn)不是很好,頻繁的重新登錄并不是一種比較好的交互方式。而如果自動(dòng)生成新的令牌則會(huì)出現(xiàn)安全問題,比如黑客獲取了一個(gè)過期的令牌并向后端發(fā)送請(qǐng)求,則也可以獲得一個(gè)更新的令牌。
為了權(quán)衡上面的問題,產(chǎn)生了一種刷新 token 的機(jī)制,當(dāng)用戶第一次登錄成功,后端會(huì)返回兩個(gè) token,一個(gè) accessToken 用來進(jìn)行請(qǐng)求,也就是我們每次請(qǐng)求都附上 accessToken,而 refreshToken 則是用來在 accessToken 過期的時(shí)候進(jìn)行 accessToken 的刷新。一般來說,accessToken 由于每次請(qǐng)求都會(huì)附上,所以安全風(fēng)險(xiǎn)比較高,所以過期時(shí)間較短,而 refreshToken 則只有在 accessToken 過期的時(shí)候才會(huì)發(fā)送到后端,所以安全風(fēng)險(xiǎn)相對(duì)較低,所以過期時(shí)間可以長(zhǎng)一點(diǎn)。
當(dāng)我們的 accessToken 過期之后,我們會(huì)向后端的 token 刷新接口請(qǐng)求并傳入 refreshToken,后端驗(yàn)證梅雨問題之后會(huì)給我們一個(gè)新的 accessToken,我們保存后就可以保證訪問的連續(xù)性。當(dāng)然,這也并非絕對(duì)安全的,只是一種相對(duì)安全一點(diǎn)的做法。一般我們將兩個(gè) token 保存在 localStorage 中。
在項(xiàng)目中我主要使用的是 axios,所以 token 的刷新以及請(qǐng)求附帶 token 都是使用的 axios 的攔截器完成的。這其中需要注意的地方有三點(diǎn):
不要重復(fù)刷新 token,即一個(gè)請(qǐng)求已經(jīng)刷新 token 了,此時(shí)可能新的 token 還沒有回來,其他請(qǐng)求不應(yīng)該重復(fù)刷新。
當(dāng)新的 token 還沒有回來的時(shí)候,其他的請(qǐng)求應(yīng)該進(jìn)行暫存,等新的 token 回來以后再一次進(jìn)行請(qǐng)求。
如果請(qǐng)求是由登錄頁(yè)面或者請(qǐng)求本身就是刷新 token 的請(qǐng)求則不需要攔截,否則會(huì)陷入死循環(huán)。
第一個(gè)問題用一個(gè) Boolean 字段加鎖即可,第二個(gè)問題將請(qǐng)求新 token 過程中發(fā)起的請(qǐng)求用狀態(tài)為 pendding 的 Promise 進(jìn)行暫存,放到一個(gè)數(shù)組中,當(dāng)新的 token 回來的時(shí)候依次 resolve 每一個(gè) pendding 的 Promise 即可。具體的代碼細(xì)節(jié)我直接貼上項(xiàng)目上的源碼:
import axios, * as AxiosInterface from 'axios'; // Token 接口,訪問 token,刷新 token 和過期時(shí) const instance = axios.create({ // baseURL: '' timeout: 300000, headers: { 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest', }, }); async function refreshAccessToken(): Promise<AxiosInterface.AxiosResponse<AxiosData>> { return await instance.post('api/refreshtoken'); } let isRefreshing = false; let requests: Array<Function> = []; // 若在 token 刷新過程中進(jìn)來多個(gè)請(qǐng)求則存入 requests 中 // axios.defaults.baseURL = 'http://127.0.0.1:8888/api/private/v1/'; // 設(shè)置請(qǐng)求攔截器,若 token 過期則刷新 token axios.interceptors.request.use(config => { const tokenObj = JSON.parse(window.localStorage.getItem('token') as string); if (config.url === 'api/login' || config.url === 'api/refreshtoken') return config; let accessToken = tokenObj.accessToken; let expireTime = tokenObj.expireTime; const refreshToken = tokenObj.refreshToken; config.headers.Authorization = accessToken; let time = Date.now(); console.log(time, expireTime); if (time > expireTime) { if (!isRefreshing) { isRefreshing = true; refreshAccessToken() .then(res => { ({ accessToken, expireTime } = res.data.data); time = Date.now(); const tokenStorage = { accessToken, refreshToken, expireTime: Number(time) + Number(expireTime), }; window.localStorage.setItem('token', JSON.stringify(tokenStorage)); isRefreshing = false; return accessToken; }) .then((accessToken: string) => { requests.forEach(cb => { cb(accessToken); }); requests = []; }) .catch((err: string) => { throw new Error(`refresh token error: {err}`); }); } // 如果是在刷新 token 時(shí)進(jìn)行的請(qǐng)求則暫存在 requests 數(shù)組中,這里需要使用一個(gè) pendding 的 Promise 來確保攔截的成功 const parallelRequest: Promise<AxiosInterface.AxiosRequestConfig> = new Promise(resolve => { requests.push((accessToken: string) => { config.headers.Authorization = accessToken; console.log(accessToken + Math.random() * 1000); resolve(config); }); }); return parallelRequest; } return config; }); export default (vue: Function) => { vue.prototype.http = axios; };
上述就是小編為大家分享的token 機(jī)制的實(shí)現(xiàn)原理是什么了,如果剛好有類似的疑惑,不妨參照上述分析進(jìn)行理解。如果想知道更多相關(guān)知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。