前端無感刷新token的實現(xiàn)步驟
Axios 無感知刷新令牌是一種在前端應用中實現(xiàn)自動刷新訪問令牌(access token)的技術(shù),確保用戶在進行 API 請求時不會因為令牌過期而中斷操作
- 訪問令牌(Access Token):用于訪問受保護資源的憑證,通常有一定的有效期。
- 刷新令牌(Refresh Token):用于獲取新的訪問令牌,當訪問令牌過期時使用。
實現(xiàn)步驟:
- 設置攔截器:在 Axios的請求攔截器中添加邏輯,檢查當前時間與令牌的過期時間。如果訪問令牌已過期但刷新令牌仍然有效,則調(diào)用刷新令牌接口獲取新的訪問令牌。
- 更新令牌存儲:一旦獲得新的訪問令牌,將其存儲到 localStorage、Vuex 或其他狀態(tài)管理工具中,以便后續(xù)請求使用新令牌。
- 重試原始請求:在成功刷新令牌后,重新發(fā)送被攔截的請求,此時使用新的訪問令牌。
XMLHttpRequest
// 創(chuàng)建 XMLHttpRequest 實例
const xhr = new XMLHttpRequest();
// 登錄成功后保存 Token 和 Refresh Token
function onLoginSuccess(response) {
localStorage.setItem('accessToken', response.data.accessToken);
localStorage.setItem('refreshToken', response.data.refreshToken);
}
// 發(fā)起請求的函數(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; // 表示刷新失敗
}
}
// 攔截響應并處理 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ā)送請求
} else {
alert('請重新登錄'); // Token 刷新失敗,可能需要用戶重新登錄
}
});
}
});
Axios
import axios from 'axios';
// 創(chuàng)建 Axios 實例
const apiClient = axios.create({
baseURL: 'https://your-api-url.com',
// 其他配置...
});
// 響應攔截器
apiClient.interceptors.response.use(response => {
return response;
}, error => {
const { response } = error;
if (response && response.status === 401) {
return refreshToken().then(refreshed => {
if (refreshed) {
// 令牌刷新成功,重試原始請求
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ù)據(jù)中包含新的訪問令牌
const newAccessToken = response.data.newAccessToken;
// 更新令牌存儲
localStorage.setItem('accessToken', newAccessToken);
// 更新 Axios 實例的 headers,以便后續(xù)請求使用新令牌
apiClient.defaults.headers.common['Authorization'] = `Bearer ${newAccessToken}`;
return true; // 表示刷新成功
} else {
return false; // 表示刷新失敗
}
});
}Fetch API
// 定義一個函數(shù)來處理Fetch請求
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) { // 假設401表示令牌過期
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);
// 重新嘗試原始請求
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ù): 這是一個封裝了Fetch API的函數(shù),它首先檢查本地存儲中的訪問令牌是否存在,并在請求頭中添加該令牌。如果響應狀態(tài)碼為401(表示令牌過期),則嘗試使用刷新令牌獲取新的訪問令牌,并重新發(fā)送原始請求。
- 刷新令牌邏輯: 在檢測到令牌過期時,函數(shù)會調(diào)用刷新令牌接口,并將新的訪問令牌存儲到本地存儲中。然后,它會重新設置請求頭中的授權(quán)信息,并重新發(fā)送原始請求。
- 錯誤處理: 如果在刷新令牌或發(fā)送請求的過程中發(fā)生錯誤,函數(shù)會拋出相應的錯誤,并在控制臺中記錄錯誤信息。
JQ
// 創(chuàng)建 JQuery 實例
const apiClient = $.ajaxSetup({
baseURL: 'https://your-api-url.com',
// 其他配置...
});
// 響應攔截器
$.ajaxSetup({
complete: function(jqXHR, textStatus) {
if (textStatus === 'error' && jqXHR.status === 401) {
return refreshToken().then(refreshed => {
if (refreshed) {
// 令牌刷新成功,重試原始請求
return apiClient.request(this);
} else {
// 令牌刷新失敗,可能需要用戶重新登錄
alert('請重新登錄');
}
});
}
}
});
// 令牌刷新函數(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ù)據(jù)中包含新的訪問令牌
const newAccessToken = response.data.newAccessToken;
// 更新令牌存儲
localStorage.setItem('accessToken', newAccessToken);
// 更新 JQuery 實例的 headers,以便后續(xù)請求使用新令牌
apiClient.defaults.headers.common['Authorization'] = `Bearer ${newAccessToken}`;
return true; // 表示刷新成功
} else {
return false; // 表示刷新失敗
}
});
}uni.request
// 導入封裝的request插件
import http from './interface';
import { getRefreshToken } from '@/common/api/apis.js'; // 刷新token接口
let isRefreshing = false; // 是否處于刷新token狀態(tài)中
let fetchApis = []; // 失效后同時發(fā)送請求的容器
let refreshCount = 0; // 限制無感刷新的最大次數(shù)
function onFetch(newToken) {
refreshCount += 1;
if (refreshCount === 3) {
refreshCount = 0;
fetchApis = [];
return Promise.reject();
}
fetchApis.forEach(callback => {
callback(newToken);
});
// 清空緩存接口
fetchApis = [];
return Promise.resolve();
}
// 響應攔截器
http.interceptor.response((response) => {
if (response.config.loading) {
uni.hideLoading();
}
// 請求成功但接口返回的錯誤處理
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: 不需要做無感刷新的白名單接口
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 => {
// 超過循環(huán)次數(shù)時,回到登錄頁,這里可以添加你執(zhí)行退出登錄的邏輯
uni.showToast({ title: '登錄失效,請重新登錄', icon: 'error' });
setTimeout(() => {
uni.reLaunch({ url: '/pages/login/login' });
}, 1500);
});
}).catch(err => {
// refreshToken接口報錯,證明refreshToken也過期了,那沒辦法啦重新登錄唄
uni.showToast({ title: '登錄失效,請重新登錄', 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;
}
});注意事項:
- 錯誤處理:確保在刷新令牌失敗時,有適當?shù)腻e誤處理機制,例如提示用戶重新登錄。
- 并發(fā)請求:處理多個請求同時需要刷新令牌的情況,避免重復刷新。
- 安全性:確保刷新令牌的安全存儲和傳輸,防止被惡意攻擊者獲取。
總結(jié)
到此這篇關(guān)于前端無感刷新token的文章就介紹到這了,更多相關(guān)前端無感刷新token內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
BootStrap的彈出框(Popover)支持鼠標移到彈出層上彈窗層不隱藏的原因及解決辦法
彈出框(Popover)與工具提示(Tooltip)類似,提供了一個擴展的視圖。本文給大家介紹BootStrap的彈出框(Popover)支持鼠標移到彈出層上彈窗層不隱藏的原因及解決辦法,喜歡的朋友參考下吧2016-04-04
javascript設計模式 – 備忘錄模式原理與用法實例分析
這篇文章主要介紹了javascript設計模式 – 備忘錄模式,結(jié)合實例形式分析了javascript備忘錄模式相關(guān)概念、原理、用法及操作注意事項,需要的朋友可以參考下2020-04-04
jquery自定義插件結(jié)合baiduTemplate.js實現(xiàn)異步刷新(附源碼)
本文主要介紹了jquery自定義插件結(jié)合baiduTemplate.js實現(xiàn)異步刷新的具體實例,具有很好的參考價值,需要的朋友一起來看下吧2016-12-12
ionic2打包android時gradle無法下載的解決方法
這篇文章主要為大家詳細介紹了ionic2打包android時gradle無法下載的解決方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-04-04
JavaScript Event Loop相關(guān)原理解析
這篇文章主要介紹了JavaScript Event Loop相關(guān)原理解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-06-06

