uniapp的双token

背景

在小程序实现双token增强安全性和优化用户的体验

思路

后端在登录的时候传过来的accessToken和refreshToken,我在uni.login的回调调用登录api

javascript 复制代码
let accessToken = result.data.data.accessToken;
let refreshToken = result.data.data.refreshToken;
let accessTime = result.data.data.accessExpiresIn;
let refreshTime = result.data.data.refreshExpiresIn;
let id = result.data.data.userId;

// 设置全局数据
getApp().globalData.token = accessToken;
getApp().globalData.login = true;

// 同步存储
uni.setStorageSync("token", accessToken);
uni.setStorageSync("id", id);
console.log(accessTime, accessToken, "access");
console.log(refreshTime, refreshToken, "refresh");

// 设置带过期时间的存储
setAdImagesWithExpiry(accessToken, accessTime, "accessToken");
setAdImagesWithExpiry(refreshToken, refreshTime, "refreshToken");

这里使用了一个工具函数,设置带时间的本地储存 在utils/timeStorage.js

javascript 复制代码
export function setAdImagesWithExpiry(list, expiresInMilliseconds, AD_IMAGES) {
	const now = new Date();
	const item = {
		value: list,
		expiry: now.getTime() + expiresInMilliseconds,
	};
	uni.setStorageSync(AD_IMAGES, JSON.stringify(item));
}

export function getAdImagesWithExpiry(AD_IMAGES, fn = () => {}) {
	const itemStr = uni.getStorageSync(AD_IMAGES);
	if (!itemStr) {
		return null;
	}
	const item = JSON.parse(itemStr);
	const now = new Date();
	if (now.getTime() > item.expiry) {
		// uni.removeStorage({
		// 	key: AD_IMAGES,
		// 	success: function() {
		// 		console.log('删除成功');
		// 	},
		// 	fail: function() {
		// 		console.log('删除失败');
		// 	}
		// });
		fn();
		return null;
	}
	return item.value;
}

因为测试的原因,把清除过期数据的代码注释了

这样就实现了定时储存和定时判断是否过期的方法

把接收到的access和refresh的token都这样进行一个储存

在封装的请求里面进行拦截401

401是我们需要做处理的状态码

整个封装的请求代码如下

javascript 复制代码
import {
	getAdImagesWithExpiry,
	setAdImagesWithExpiry
} from "./timeStorage";

const BASE_URL = "https://localhost:8080"
const ACCESS_TOKEN_EXPIRED = "ACCESS_TOKEN_EXPIRED" // 需要执行短token刷新
const AUTH_INVALID = "AUTH_INVALID" // 需要跳转登录
const TOKEN_INVALID = "TOKEN_INVALID" // 需要清除本地的所有用户信息

// 不需要Token检查的URL列表
const NO_TOKEN_URLS = [
	'/user/user/login',
];

// 判断是否是不需要检查token的url
const isNoTokenUrl = (url) => {
	return NO_TOKEN_URLS.some(pattern => {
		if (typeof pattern === 'string') {
			return url.includes(pattern);
		}
		return false;
	});
};

// 刷新状态管理
let isRefreshing = false; // 是否正在刷新Token
let refreshSubscribers = []; // 等待Token刷新的请求队列

// 添加请求到等待队列
const addRefreshSubscriber = (callback) => {
	refreshSubscribers.push(callback);
};

// 执行等待队列中的请求
const onRefreshed = (token) => {
	refreshSubscribers.forEach(callback => callback(token));
	refreshSubscribers = [];
};

// 刷新Token的函数
const refreshToken = async () => {
	console.log('正在调用刷新Token的方法...');
	const refreshTokenValue = getAdImagesWithExpiry("refreshToken");
	console.log(refreshTokenValue, "token");

	const response = await uni.request({
		url: BASE_URL + '/user/user/refresh-token?refreshToken=' + encodeURIComponent(refreshTokenValue),
		method: 'POST',
	});

	console.log(response, "刷新的token的返回值");

	if (response.data.code === 1) {
		let accessToken = response.data.data.accessToken;
		let accessTime = response.data.data.accessExpiresIn;
		let id = response.data.data.userId;

		getApp().globalData.token = accessToken;
		setAdImagesWithExpiry(accessToken, accessTime, "accessToken");
		uni.setStorageSync("token", accessToken);
		uni.setStorageSync("id", id);
		getApp().globalData.login = true;

		console.log('Token刷新成功,新的Token已保存。');
		return true;
	} else {
		console.log('刷新Token失败', response);
		return false;
	}
};

// 处理Token刷新流程
const handleTokenRefresh = async () => {
	// 如果已经在刷新,直接返回Promise等待刷新完成
	if (isRefreshing) {
		return new Promise((resolve) => {
			addRefreshSubscriber((token) => {
				resolve(token);
			});
		});
	}

	isRefreshing = true;

	const refreshSuccess = await refreshToken();

	if (refreshSuccess) {
		// 刷新成功,通知所有等待的请求
		onRefreshed(getApp().globalData.token);
		return true;
	} else {
		// 刷新失败,清空队列并跳转登录
		refreshSubscribers = [];
		redirectToLogin();
		return false;
	}

	isRefreshing = false;
};

// 跳转到登录页
const redirectToLogin = () => {
	try {
		getApp().globalData.token = '';
		getApp().globalData.login = false;
	} catch (e) {
		console.error('清除缓存失败', e);
	}
	uni.reLaunch({
		url: '/pages/index/index'
	});
};

// 重试原始请求
const retryOriginalRequest = async (originalRequest) => {
	return new Promise((resolve, reject) => {
		uni.request({
			url: originalRequest.url,
			method: originalRequest.method,
			header: {
				...originalRequest.header,
				'authentication': getApp().globalData.token || '',
			},
			data: originalRequest.data,
			timeout: originalRequest.timeout || 10000,
			complete: (res) => {
				if (res.statusCode === 200) {
					resolve(res);
				} else {
					reject(new Error(`重试请求失败: ${res.statusCode}`));
				}
			}
		});
	});
};

const request = async (method, url, data = {}, config = {}) => {
	try {
		// 如果是免Token URL,直接请求
		if (isNoTokenUrl(url)) {
			return await new Promise((resolve, reject) => {
				uni.request({
					url: BASE_URL + url,
					method: method.toUpperCase(),
					header: {
						'content-Type': config.contentType || 'application/json',
					},
					data: data,
					timeout: config.timeout || 10000,
					complete: (res) => {
						console.log(res, "登录过程的参数")
						if (res.statusCode === 200) {
							resolve(res);
						} else {
							reject(new Error(`请求失败: ${res.statusCode}`));
						}
					}
				});
			});
		}


                //用promise进行管理,特别是success的回调要用async...await这个,把所有错误统一起来进行处理
		return await new Promise((resolve, reject) => {
			uni.request({
				url: BASE_URL + url,
				method: method.toUpperCase(),
				header: {
					'content-Type': config.contentType || 'application/json',
					'authentication': getApp().globalData.token || '',
				},
				data: data,
				timeout: config.timeout || 10000,
				success: async (res) => {
					try {
						if (res.statusCode === 200) {
							resolve(res);
							return;
						}

						if (res.statusCode === 401 || (res?.data && res?.data?.code === 401)) {
							const errorType = res.data?.error;
							console.log(res.data, "401错误详情");

							switch (errorType) {
								case ACCESS_TOKEN_EXPIRED:
									// 保存原始请求信息
									const originalRequest = {
										url: BASE_URL + url,
										method: method.toUpperCase(),
										header: {
											'content-Type': config.contentType || 'application/json',
											'authentication': getApp().globalData.token || '',
										},
										data: data,
										timeout: config.timeout || 10000
									};

									// 如果正在刷新,加入等待队列
									if (isRefreshing) {
										console.log('Token正在刷新中,加入等待队列');
										addRefreshSubscriber(async (newToken) => {
											try {
												const retryResponse = await retryOriginalRequest(originalRequest);
												resolve(retryResponse);
											} catch (error) {
												reject(error);
											}
										});
										return;
									}

									// 执行Token刷新
									const refreshSuccess = await handleTokenRefresh();

									if (refreshSuccess) {
										console.log('Token刷新成功,重新发起原请求');
										try {
											const retryResponse = await retryOriginalRequest(originalRequest);
											resolve(retryResponse);
										} catch (error) {
											reject(error);
										}
									} else {
										reject(new Error('Token刷新失败'));
									}
									break;

								case AUTH_INVALID:
									console.log(errorType)
									redirectToLogin();
									break;
								case TOKEN_INVALID:
									getApp().globalData.token = '';
									getApp().globalData.login = false;
									uni.clearStorage()
									// 清除全部和用户有关的数据
									break

								default:
									reject(new Error(`认证错误: ${errorType}`));
							}
						} else {
							reject(new Error(`请求失败: ${res.statusCode}`));
						}
					} catch (error) {
						reject(error);
					}
				}
			});
		});

	} catch (err) {
		console.error('请求失败:', err);
		uni.showToast({
			title: '网络请求失败',
			icon: 'none'
		});
		throw err;
	}
};

export const http = {
	get: (url, data = {}, config) => request('GET', url, data, config),
	post: (url, data, config) => request('POST', url, data, config),
	put: (url, data, config) => request('PUT', url, data, config),
	delete: (url, data, config) => request('DELETE', url, data, config)
};
//把这个导出,挂载到全局上面去,方便使用

// 导出刷新状态,方便调试
export const getRefreshStatus = () => ({
	isRefreshing,
	queueLength: refreshSubscribers.length
});

以上便是在uniapp完成双token的代码了.谢谢观看

相关推荐
正义的大古3 小时前
OpenLayers地图交互 -- 章节六:范围交互详解
前端·javascript·vue.js·openlayers
訾博ZiBo3 小时前
【文本朗读小工具】- 快速、免费的智能语音合成工具
前端
aopstudio3 小时前
零成本上线动态博客:用 Rin + Cloudflare 部署个人博客的完整指南
javascript·serverless·github
天蓝色的鱼鱼3 小时前
低代码是“未来”还是“骗局”?前端开发者有话说
前端
答案answer4 小时前
three.js着色器(Shader)实现数字孪生项目中常见的特效
前端·three.js
用户6120414922134 小时前
支持eclipse+idea+mysql5和8的javaweb学生信息管理系统
java·javascript·后端
城管不管4 小时前
SpringBoot与反射
java·开发语言·前端
JackJiang4 小时前
即时通讯安全篇(三):一文读懂常用加解密算法与网络通讯安全
前端
一直_在路上4 小时前
Go架构师实战:玩转缓存,击破医疗IT百万QPS与“三大天灾
前端·面试