前端無(wú)感刷新token的實(shí)現(xiàn)步驟
Axios 無(wú)感知刷新令牌是一種在前端應(yīng)用中實(shí)現(xiàn)自動(dòng)刷新訪問(wèn)令牌(access token)的技術(shù),確保用戶在進(jìn)行 API 請(qǐng)求時(shí)不會(huì)因?yàn)榱钆七^(guò)期而中斷操作
- 訪問(wèn)令牌(Access Token):用于訪問(wèn)受保護(hù)資源的憑證,通常有一定的有效期。
- 刷新令牌(Refresh Token):用于獲取新的訪問(wèn)令牌,當(dāng)訪問(wèn)令牌過(guò)期時(shí)使用。
實(shí)現(xiàn)步驟:
- 設(shè)置攔截器:在 Axios的請(qǐng)求攔截器中添加邏輯,檢查當(dāng)前時(shí)間與令牌的過(guò)期時(shí)間。如果訪問(wèn)令牌已過(guò)期但刷新令牌仍然有效,則調(diào)用刷新令牌接口獲取新的訪問(wèn)令牌。
- 更新令牌存儲(chǔ):一旦獲得新的訪問(wèn)令牌,將其存儲(chǔ)到 localStorage、Vuex 或其他狀態(tài)管理工具中,以便后續(xù)請(qǐng)求使用新令牌。
- 重試原始請(qǐng)求:在成功刷新令牌后,重新發(fā)送被攔截的請(qǐng)求,此時(shí)使用新的訪問(wèn)令牌。
XMLHttpRequest
// 創(chuàng)建 XMLHttpRequest 實(shí)例
const xhr = new XMLHttpRequest();
// 登錄成功后保存 Token 和 Refresh Token
function onLoginSuccess(response) {
localStorage.setItem('accessToken', response.data.accessToken);
localStorage.setItem('refreshToken', response.data.refreshToken);
}
// 發(fā)起請(qǐng)求的函數(shù)
function sendRequest(url, method, data) {
return new Promise((resolve, reject) => {
xhr.open(method, url);
xhr.setRequestHeader('Authorization', `Bearer ${localStorage.getItem('accessToken')}`);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
resolve(JSON.parse(xhr.responseText));
} else {
reject({ status: xhr.status, response: xhr.responseText });
}
}
};
if (method === 'POST' && data) {
xhr.send(JSON.stringify(data));
} else {
xhr.send();
}
});
}
// 刷新 Token 的函數(shù)
async function refreshToken() {
const refreshToken = localStorage.getItem('refreshToken');
const response = await fetch('/path/to/refresh', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ refresh_token: refreshToken }),
});
const res = await response.json();
if (res.success) {
localStorage.setItem('accessToken', res.data.newAccessToken);
return true; // 表示刷新成功
} else {
return false; // 表示刷新失敗
}
}
// 攔截響應(yīng)并處理 Token 刷新
xhr.addEventListener('readystatechange', function() {
if (xhr.readyState === 4 && xhr.status === 401) {
refreshToken().then(refreshed => {
if (refreshed) {
xhr.setRequestHeader('Authorization', `Bearer ${localStorage.getItem('accessToken')}`);
xhr.send(); // 重新發(fā)送請(qǐng)求
} else {
alert('請(qǐng)重新登錄'); // Token 刷新失敗,可能需要用戶重新登錄
}
});
}
});
Axios
import axios from 'axios';
// 創(chuàng)建 Axios 實(shí)例
const apiClient = axios.create({
baseURL: 'https://your-api-url.com',
// 其他配置...
});
// 響應(yīng)攔截器
apiClient.interceptors.response.use(response => {
return response;
}, error => {
const { response } = error;
if (response && response.status === 401) {
return refreshToken().then(refreshed => {
if (refreshed) {
// 令牌刷新成功,重試原始請(qǐng)求
return apiClient.request(error.config);
} else {
// 令牌刷新失敗,可能需要用戶重新登錄
return Promise.reject(error);
}
});
}
return Promise.reject(error);
});
// 令牌刷新函數(shù)
function refreshToken() {
return apiClient.post('/path/to/refresh', {
// 刷新令牌所需的參數(shù),例如 refresh_token
}).then(response => {
if (response.data.success) {
// 假設(shè)響應(yīng)數(shù)據(jù)中包含新的訪問(wèn)令牌
const newAccessToken = response.data.newAccessToken;
// 更新令牌存儲(chǔ)
localStorage.setItem('accessToken', newAccessToken);
// 更新 Axios 實(shí)例的 headers,以便后續(xù)請(qǐng)求使用新令牌
apiClient.defaults.headers.common['Authorization'] = `Bearer ${newAccessToken}`;
return true; // 表示刷新成功
} else {
return false; // 表示刷新失敗
}
});
}Fetch API
// 定義一個(gè)函數(shù)來(lái)處理Fetch請(qǐng)求
async function fetchWithToken(url, options = {}) {
const token = localStorage.getItem('token');
if (token) {
options.headers = {
...options.headers,
'Authorization': `Bearer ${token}`
};
}
try {
const response = await fetch(url, options);
if (response.status === 401) { // 假設(shè)401表示令牌過(guò)期
const refreshToken = localStorage.getItem('refreshToken');
if (!refreshToken) {
throw new Error('No refresh token available');
}
// 調(diào)用刷新令牌接口
const refreshResponse = await fetch('/api/refresh-token', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ refreshToken })
});
if (refreshResponse.ok) {
const data = await refreshResponse.json();
localStorage.setItem('token', data.newAccessToken);
// 重新嘗試原始請(qǐng)求
options.headers['Authorization'] = `Bearer ${data.newAccessToken}`;
return fetch(url, options);
} else {
throw new Error('Failed to refresh token');
}
}
return response;
} catch (error) {
console.error('Fetch error:', error);
throw error;
}
}
// 使用示例
fetchWithToken('/api/protected-resource')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));- fetchWithToken函數(shù): 這是一個(gè)封裝了Fetch API的函數(shù),它首先檢查本地存儲(chǔ)中的訪問(wèn)令牌是否存在,并在請(qǐng)求頭中添加該令牌。如果響應(yīng)狀態(tài)碼為401(表示令牌過(guò)期),則嘗試使用刷新令牌獲取新的訪問(wèn)令牌,并重新發(fā)送原始請(qǐng)求。
- 刷新令牌邏輯: 在檢測(cè)到令牌過(guò)期時(shí),函數(shù)會(huì)調(diào)用刷新令牌接口,并將新的訪問(wèn)令牌存儲(chǔ)到本地存儲(chǔ)中。然后,它會(huì)重新設(shè)置請(qǐng)求頭中的授權(quán)信息,并重新發(fā)送原始請(qǐng)求。
- 錯(cuò)誤處理: 如果在刷新令牌或發(fā)送請(qǐng)求的過(guò)程中發(fā)生錯(cuò)誤,函數(shù)會(huì)拋出相應(yīng)的錯(cuò)誤,并在控制臺(tái)中記錄錯(cuò)誤信息。
JQ
// 創(chuàng)建 JQuery 實(shí)例
const apiClient = $.ajaxSetup({
baseURL: 'https://your-api-url.com',
// 其他配置...
});
// 響應(yīng)攔截器
$.ajaxSetup({
complete: function(jqXHR, textStatus) {
if (textStatus === 'error' && jqXHR.status === 401) {
return refreshToken().then(refreshed => {
if (refreshed) {
// 令牌刷新成功,重試原始請(qǐng)求
return apiClient.request(this);
} else {
// 令牌刷新失敗,可能需要用戶重新登錄
alert('請(qǐng)重新登錄');
}
});
}
}
});
// 令牌刷新函數(shù)
function refreshToken() {
return $.ajax({
url: '/path/to/refresh',
method: 'POST',
data: {
refresh_token: localStorage.getItem('refreshToken')
},
dataType: 'json'
}).then(response => {
if (response.data.success) {
// 假設(shè)響應(yīng)數(shù)據(jù)中包含新的訪問(wèn)令牌
const newAccessToken = response.data.newAccessToken;
// 更新令牌存儲(chǔ)
localStorage.setItem('accessToken', newAccessToken);
// 更新 JQuery 實(shí)例的 headers,以便后續(xù)請(qǐng)求使用新令牌
apiClient.defaults.headers.common['Authorization'] = `Bearer ${newAccessToken}`;
return true; // 表示刷新成功
} else {
return false; // 表示刷新失敗
}
});
}uni.request
// 導(dǎo)入封裝的request插件
import http from './interface';
import { getRefreshToken } from '@/common/api/apis.js'; // 刷新token接口
let isRefreshing = false; // 是否處于刷新token狀態(tài)中
let fetchApis = []; // 失效后同時(shí)發(fā)送請(qǐng)求的容器
let refreshCount = 0; // 限制無(wú)感刷新的最大次數(shù)
function onFetch(newToken) {
refreshCount += 1;
if (refreshCount === 3) {
refreshCount = 0;
fetchApis = [];
return Promise.reject();
}
fetchApis.forEach(callback => {
callback(newToken);
});
// 清空緩存接口
fetchApis = [];
return Promise.resolve();
}
// 響應(yīng)攔截器
http.interceptor.response((response) => {
if (response.config.loading) {
uni.hideLoading();
}
// 請(qǐng)求成功但接口返回的錯(cuò)誤處理
if (response.data.statusCode && +response.data.statusCode !== 200) {
if (!response.config.needPromise) {
console.log('error', response);
uni.showModal({
title: '提示',
content: response.data.message,
showCancel: false,
confirmText: '知道了'
});
// 中斷
return new Promise(() => {});
} else {
// reject Promise
return Promise.reject(response.data);
}
}
return response;
}, (error) => {
const token = uni.getStorageSync('token');
const refreshToken = uni.getStorageSync('refreshToken');
// DESC: 不需要做無(wú)感刷新的白名單接口
const whiteFetchApi = ['/dealersystem/jwtLogin', '/dealersystem/smsLogin', '/sso2/login', '/dealersystem/isLogin'];
switch (error.statusCode) {
case 401:
case 402:
if (token && !whiteFetchApi.includes(error.config.url)) {
if (!isRefreshing) {
isRefreshing = true;
getRefreshToken({ refreshToken }).then(res => {
let newToken = res.data;
onTokenFetched(newToken).then(res => {}).catch(err => {
// 超過(guò)循環(huán)次數(shù)時(shí),回到登錄頁(yè),這里可以添加你執(zhí)行退出登錄的邏輯
uni.showToast({ title: '登錄失效,請(qǐng)重新登錄', icon: 'error' });
setTimeout(() => {
uni.reLaunch({ url: '/pages/login/login' });
}, 1500);
});
}).catch(err => {
// refreshToken接口報(bào)錯(cuò),證明refreshToken也過(guò)期了,那沒(méi)辦法啦重新登錄唄
uni.showToast({ title: '登錄失效,請(qǐng)重新登錄', icon: 'error' });
setTimeout(() => {
uni.reLaunch({ url: '/pages/login/login' });
}, 1500);
}).finally(() => { isRefreshing = false });
}
return new Promise((resolve) => { // 此處的promise很關(guān)鍵,就是確保你的接口返回值在此處resolve,以便后續(xù)代碼執(zhí)行
addFetchApi((newToken) => {
error.config.header['Authorization'] = `Bearer ${newToken}`;
http.request(error.config).then(response => {
resolve(response);
});
});
});
}
break;
default:
break;
}
});注意事項(xiàng):
- 錯(cuò)誤處理:確保在刷新令牌失敗時(shí),有適當(dāng)?shù)腻e(cuò)誤處理機(jī)制,例如提示用戶重新登錄。
- 并發(fā)請(qǐng)求:處理多個(gè)請(qǐng)求同時(shí)需要刷新令牌的情況,避免重復(fù)刷新。
- 安全性:確保刷新令牌的安全存儲(chǔ)和傳輸,防止被惡意攻擊者獲取。
總結(jié)
到此這篇關(guān)于前端無(wú)感刷新token的文章就介紹到這了,更多相關(guān)前端無(wú)感刷新token內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaScript實(shí)現(xiàn)可拖拽的拖動(dòng)層Div實(shí)例
這篇文章主要介紹了JavaScript實(shí)現(xiàn)可拖拽的拖動(dòng)層Div的方法,拖拽頁(yè)面中的div塊可實(shí)現(xiàn)div塊按照拖動(dòng)軌跡移動(dòng)的效果,涉及javascript鼠標(biāo)事件、頁(yè)面元素樣式結(jié)合事件函數(shù)動(dòng)態(tài)操作的相關(guān)技巧,需要的朋友可以參考下2015-08-08
用JS實(shí)現(xiàn)HTML標(biāo)簽替換效果
用JS實(shí)現(xiàn)HTML標(biāo)簽替換效果...2007-06-06
JS中將圖片base64轉(zhuǎn)file文件的兩種方式
這篇文章主要介紹了JS中圖片base64轉(zhuǎn)file文件的兩種方式,實(shí)現(xiàn)把圖片的base64編碼轉(zhuǎn)成file文件的功能,然后再上傳至服務(wù)器,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-02-02
js實(shí)現(xiàn)界面向原生界面發(fā)消息并跳轉(zhuǎn)功能
這篇文章主要為大家詳細(xì)介紹了js實(shí)現(xiàn)界面向原生界面發(fā)消息并跳轉(zhuǎn)功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-11-11
jquery獲取img的src值的簡(jiǎn)單實(shí)例
下面小編就為大家?guī)?lái)一篇jquery獲取img的src值的簡(jiǎn)單實(shí)例。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-05-05

