登錄獲取Token和刷新Token的完整實(shí)現(xiàn)流程
登錄獲取 Token 和刷新 Token 是兩個(gè)配合使用的接口,下面是完整的使用流程和代碼實(shí)現(xiàn)
一、兩個(gè)接口的作用
| 接口類型 | 使用時(shí)機(jī) | 返回內(nèi)容 | 有效期 |
|---|---|---|---|
| 登錄接口 | 用戶首次登錄 | accessToken + refreshToken | accessToken 短期(如30分鐘) refreshToken 長(zhǎng)期(如7天) |
| 刷新Token接口 | accessToken 過期時(shí) | 新的 accessToken + 新的 refreshToken(可選) | 重新計(jì)時(shí) |
二、完整流程圖
用戶登錄
↓
調(diào)用登錄接口 → 獲取 accessToken + refreshToken
↓
存儲(chǔ)到本地
↓
正常請(qǐng)求業(yè)務(wù)接口(攜帶 accessToken)
↓
┌─────────────────┐
│ 接口返回401過期?│
└─────────────────┘
↓ 是
調(diào)用刷新Token接口(攜帶 refreshToken)
↓
獲取新的 accessToken
↓
更新本地存儲(chǔ)
↓
重試剛才失敗的請(qǐng)求
↓
繼續(xù)正常請(qǐng)求...
三、完整代碼實(shí)現(xiàn)
1. 定義 API 接口
// api/auth.js
const BASE_URL = 'https://your-api.com';
export default {
// 登錄接口
login(data) {
return uni.request({
url: `${BASE_URL}/login`,
method: 'POST',
data: data
});
},
// 刷新Token接口
refreshToken(refreshToken) {
return uni.request({
url: `${BASE_URL}/refresh`,
method: 'POST',
data: { refreshToken: refreshToken }
});
}
};
2. 登錄邏輯
// pages/login/login.vue
export default {
data() {
return {
username: '',
password: ''
}
},
methods: {
async handleLogin() {
try {
// 1. 調(diào)用登錄接口
const res = await this.$api.login({
username: this.username,
password: this.password
});
if (res.code === 0) {
const { accessToken, refreshToken, expiresIn, tokenType } = res.data;
// 2. 存儲(chǔ)Token
this.saveToken({
accessToken,
refreshToken,
expiresIn, // 有效期時(shí)長(zhǎng)(毫秒)
tokenType // Bearer 或 null
});
// 3. 跳轉(zhuǎn)到首頁
uni.switchTab({
url: '/pages/index/index'
});
} else {
uni.showToast({
title: res.msg || '登錄失敗',
icon: 'none'
});
}
} catch (error) {
console.error('登錄失敗', error);
}
},
// 存儲(chǔ)Token
saveToken(tokenData) {
const now = Date.now();
// 存儲(chǔ)基礎(chǔ)信息
uni.setStorageSync('accessToken', tokenData.accessToken);
uni.setStorageSync('refreshToken', tokenData.refreshToken);
uni.setStorageSync('tokenType', tokenData.tokenType || '');
// 計(jì)算過期時(shí)間(絕對(duì)時(shí)間戳)
if (tokenData.expiresIn) {
uni.setStorageSync('expiresTime', now + tokenData.expiresIn);
} else if (tokenData.expiresTime) {
uni.setStorageSync('expiresTime', tokenData.expiresTime);
}
}
}
};
3. 刷新Token邏輯
// utils/refreshToken.js
// 防止多個(gè)請(qǐng)求同時(shí)刷新Token(鎖機(jī)制)
let isRefreshing = false;
let refreshSubscribers = [];
// 待刷新期間緩存的請(qǐng)求
function subscribeTokenRefresh(callback) {
refreshSubscribers.push(callback);
}
function onTokenRefreshed(newToken) {
refreshSubscribers.forEach(callback => callback(newToken));
refreshSubscribers = [];
}
// 刷新Token
async function refreshAccessToken() {
try {
const refreshToken = uni.getStorageSync('refreshToken');
if (!refreshToken) {
// 沒有refreshToken,跳轉(zhuǎn)登錄
redirectToLogin();
return null;
}
// 調(diào)用刷新接口
const res = await uni.request({
url: 'https://your-api.com/refresh',
method: 'POST',
data: { refreshToken: refreshToken }
});
if (res.statusCode === 200 && res.data.code === 0) {
const { accessToken, refreshToken: newRefreshToken, expiresIn } = res.data.data;
// 更新存儲(chǔ)
uni.setStorageSync('accessToken', accessToken);
uni.setStorageSync('expiresTime', Date.now() + expiresIn);
// 如果返回了新的refreshToken(通常刷新接口也會(huì)返回新的)
if (newRefreshToken) {
uni.setStorageSync('refreshToken', newRefreshToken);
}
// 通知所有等待的請(qǐng)求
onTokenRefreshed(accessToken);
return accessToken;
} else {
// 刷新失敗,清空Token并跳轉(zhuǎn)登錄
clearTokenAndRedirect();
return null;
}
} catch (error) {
console.error('刷新Token失敗', error);
clearTokenAndRedirect();
return null;
} finally {
isRefreshing = false;
}
}
// 跳轉(zhuǎn)登錄頁
function redirectToLogin() {
clearToken();
uni.reLaunch({
url: '/pages/login/login'
});
}
// 清空Token
function clearToken() {
uni.removeStorageSync('accessToken');
uni.removeStorageSync('refreshToken');
uni.removeStorageSync('expiresTime');
uni.removeStorageSync('tokenType');
}
// 導(dǎo)出刷新方法
export { refreshAccessToken, clearToken, redirectToLogin };
4. 請(qǐng)求攔截器(自動(dòng)處理Token過期)
// utils/request.js
import { refreshAccessToken, redirectToLogin } from './refreshToken.js';
// 請(qǐng)求隊(duì)列(存放過期期間等待的請(qǐng)求)
let requestQueue = [];
let isRefreshing = false;
// 發(fā)起請(qǐng)求的核心方法
function request(config) {
return new Promise((resolve, reject) => {
// 1. 檢查Token是否過期
const expiresTime = uni.getStorageSync('expiresTime');
const now = Date.now();
// 判斷是否需要刷新Token
if (expiresTime && now >= expiresTime) {
// Token已過期,需要刷新
if (!isRefreshing) {
isRefreshing = true;
// 刷新Token
refreshAccessToken().then(newToken => {
if (newToken) {
// 刷新成功,執(zhí)行隊(duì)列中的請(qǐng)求
requestQueue.forEach(cb => cb(newToken));
requestQueue = [];
// 重新發(fā)起當(dāng)前請(qǐng)求
doRequest(config).then(resolve).catch(reject);
} else {
reject({ message: 'Token刷新失敗' });
}
isRefreshing = false;
}).catch(() => {
isRefreshing = false;
reject({ message: 'Token刷新失敗' });
});
}
// 將當(dāng)前請(qǐng)求加入隊(duì)列,等刷新完成后重試
requestQueue.push((newToken) => {
// 更新config中的token
config.header = getAuthHeader(newToken);
doRequest(config).then(resolve).catch(reject);
});
} else {
// Token未過期,直接發(fā)起請(qǐng)求
config.header = getAuthHeader();
doRequest(config).then(resolve).catch(reject);
}
});
}
// 實(shí)際發(fā)起請(qǐng)求
function doRequest(config) {
return new Promise((resolve, reject) => {
uni.request({
url: config.url,
method: config.method || 'GET',
data: config.data,
header: config.header,
success: (res) => {
// 處理業(yè)務(wù)錯(cuò)誤(如Token無效)
if (res.data && res.data.code === 401) {
// Token無效,強(qiáng)制刷新
refreshAccessToken().then(() => {
// 重試請(qǐng)求
request(config).then(resolve).catch(reject);
}).catch(() => {
redirectToLogin();
reject({ message: '登錄已過期' });
});
} else {
resolve(res);
}
},
fail: (err) => {
reject(err);
}
});
});
}
// 獲取認(rèn)證請(qǐng)求頭
function getAuthHeader(customToken = null) {
const token = customToken || uni.getStorageSync('accessToken');
const tokenType = uni.getStorageSync('tokenType');
if (tokenType === 'Bearer') {
return { 'Authorization': `Bearer ${token}` };
} else {
return { 'accessToken': token };
}
}
export default request;
5. 實(shí)際使用示例
// 在 main.js 中全局掛載
import request from '@/utils/request.js';
Vue.prototype.$request = request;
// 在頁面中使用
// pages/index/index.vue
export default {
async mounted() {
try {
// 自動(dòng)處理Token過期和刷新
const res = await this.$request({
url: 'https://your-api.com/user/info',
method: 'GET'
});
console.log('用戶信息:', res.data);
} catch (error) {
console.error('請(qǐng)求失敗:', error);
if (error.message === '登錄已過期') {
// 跳轉(zhuǎn)到登錄頁
uni.navigateTo({
url: '/pages/login/login'
});
}
}
}
};
四、關(guān)鍵點(diǎn)總結(jié)
1. 刷新策略
- 主動(dòng)刷新:每次請(qǐng)求前檢查,剩余時(shí)間 < 5分鐘就提前刷新
- 被動(dòng)刷新:請(qǐng)求返回401時(shí)觸發(fā)刷新
2. 并發(fā)控制(重要)
// 避免短時(shí)間內(nèi)多次刷新Token let isRefreshing = false; // 鎖標(biāo)志 let requestQueue = []; // 等待隊(duì)列
3. 刷新時(shí)機(jī)
// 在請(qǐng)求攔截器中判斷
const expiresTime = uni.getStorageSync('expiresTime');
const timeRemaining = expiresTime - Date.now();
if (timeRemaining < 5 * 60 * 1000) { // 剩余不足5分鐘
await refreshAccessToken(); // 提前刷新
}
4. Token失效后處理
// 刷新接口也返回401時(shí),清空所有Token并跳轉(zhuǎn)登錄
if (res.statusCode === 401) {
uni.removeStorageSync('accessToken');
uni.removeStorageSync('refreshToken');
uni.reLaunch({ url: '/pages/login/login' });
}
五、注意事項(xiàng)
- refreshToken 也有有效期,通常比 accessToken 長(zhǎng)(如7天、30天),過期后需要重新登錄
- 刷新接口也要做防抖,避免短時(shí)間內(nèi)多次調(diào)用
- 存儲(chǔ)到本地時(shí)建議加密(特別是 refreshToken)
- 登出時(shí)要同時(shí)清空 accessToken 和 refreshToken
- 刷新Token接口建議使用 POST 請(qǐng)求,并將 refreshToken 放在 Body 中
按照以上方案實(shí)現(xiàn),就能完美處理 Token 的獲取和自動(dòng)刷新了。
以上就是登錄獲取Token和刷新Token的完整實(shí)現(xiàn)流程的詳細(xì)內(nèi)容,更多關(guān)于登錄獲取Token和刷新Token的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JS 對(duì)象(Object)和字符串(String)互轉(zhuǎn)方法
下面小編就為大家?guī)硪黄狫S 對(duì)象(Object)和字符串(String)互轉(zhuǎn)方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-05-05
JS實(shí)現(xiàn)元素上下左右移動(dòng)效果
這篇文章主要為大家詳細(xì)介紹了JS實(shí)現(xiàn)元素上下左右移動(dòng)效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-10-10
javascript中toFixed()四舍五入使用方法詳解
最近做的項(xiàng)目涉及到金額的計(jì)算,有一種方式就是進(jìn)行四舍五入的規(guī)則進(jìn)行小數(shù)點(diǎn)后面的尾數(shù)處理,以前一直以為toFixed方法就是四舍五入的,這里為大家分享一下2018-09-09
js?window.addEventListener?簡(jiǎn)介
window.addEventListener是JavaScript中用于添加事件監(jiān)聽器的方法,允許在發(fā)生特定事件時(shí)執(zhí)行函數(shù),本文給大家介紹js?window.addEventListener?是什么,感興趣的朋友一起看看吧2024-09-09
理解Javascript_09_Function與Object
在《理解Javascript_08_函數(shù)對(duì)象》中講解了很多函數(shù)對(duì)象的問題,同時(shí)也留下了許多疑問,今天讓我們來解答部分問題。2010-10-10
js實(shí)現(xiàn)仿百度風(fēng)云榜可重復(fù)多次調(diào)用的TAB切換選項(xiàng)卡效果
這篇文章主要介紹了js實(shí)現(xiàn)仿百度風(fēng)云榜可重復(fù)多次調(diào)用的TAB切換選項(xiàng)卡效果,涉及javascript鼠標(biāo)事件及頁面元素遍歷調(diào)用的實(shí)現(xiàn)技巧,非常簡(jiǎn)單實(shí)用,需要的朋友可以參考下2015-08-08

