前端双token策略(uni-app-vue3-ts版)
前端双token策略(uniapp-vue3-ts版)
前言
前面写了一篇详细介绍JWT相关的文章,其中提到了JWT中使用双token的作用,这里简单回顾一下:
简单来说也是为了减轻JWT被泄露而造成的影响,具体来说分为
refresh token
和access token
access token | refresh token | |
---|---|---|
有效时长 | 较短(如半小时) | 较长(如一天) |
作用 | 验证用户是否有操作权限 | 获取access token |
什么时候使用 | 每次需要用户登录态时传递该token | access token失效时使用 |
这样做的好处就是:
access token
频繁传输,泄露风险较大,所以将其有效期设为较短可以有效降低泄露而造成的影响,比如此时攻击方最多伪装你半个小时;access token
存在时间较短,需要频繁获取新的,为了降低用户登录次数,提高用户体验,使用refresh token
调用相关接口获取最新的access token
。refresh token
存在时间长,泄露后影响较大,所以只有在access token
失效时才传递,所以并不会频繁传输,即泄露风险较小主要就是兼顾泄露token的风险与泄露token的影响
由此可以看出双token的实现是很有必要的,所以本文将从前端角度介绍一下相关的策略,当后端有如下接口时:
- 登录成功后返回
accessToken
与refreshToken
- 携带
refreshToken
调用刷新Token的接口返回accessToken
与refreshToken
由于笔者使用的是
GraphQL
接口,担心有些读者可能没有使用过,所以上面仅用文字描述接口
策略概述
-
发起一个正常请求,如获取用户的资料详情
-
检查
accessToken
是否过期,这里是通过其中的expire
字段,后续会详细谈到 -
如果没有过期,则直接在
header
上添加该字段,就可以表明自己的身份了,从而正常请求 -
如果已经过期,则判断
refreshToken
是否过期 -
如果
refreshToken
没有过期,则携带该token
进行refresh
请求,获取新的双token
并保存 -
携带新的
accessToken
进行请求 -
如果已经过期,则要求用户重新登录
为了实现上述策略,我们先准备几个通用函数,比如获取token
中的expire
字段、保存token
等通用函数
实现浏览器的atob函数
简单介绍一下atob
函数,它是用来解码base64
字符串的,因为为了方便网络传输,JWT是进行了base64
编码的,所以想要获取JWT中携带的相关信息,就需要先使用atob
解码。
但是在微信小程序中,你可以发现在微信开发工具中可以调用该atob
函数,但到真机演示的时候就无法调用了,会显示atob
不存在,所以这里我们需要自实现一个atob
函数,比如在/utils/base64.ts
文件中:
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 /=';
export function atob(input: string) {
var str = (String (input)).replace (/[=] $/, ''); // #31: ExtendScript bad parse of /=
if (str.length % 4 === 1) {
throw new Error ("'atob' failed: The string to be decoded is not correctly encoded.");
}
for (
// initialize result and counters
var bc = 0, bs, buffer, idx = 0, output = '';
// get next character
buffer = str.charAt (idx ); // eslint-disable-line no-cond-assign
// character found in table? initialize bit storage and add its ascii value;
// @ts-ignore
~buffer && (bs = bc % 4 ? bs * 64 buffer : buffer,
// and if not first of each 4 characters,
// convert the first 8 bits to one ascii character
bc % 4) ? output = String.fromCharCode (255 & bs >> (-2 * bc & 6)) : 0
) {
// try to find character in table (0-63, not found => -1)
buffer = chars.indexOf (buffer);
}
return output;
}
如上函数改写自该仓库的代码实现
如下是该函数的调用效果演示:
当然前提是后端返回的JWT是包含了该字段的,该字段也是规范中的字段,赶紧叫你的后端加上该字段吧,又不麻烦[doge]:
实现操作token的通用函数
同样是在utils/auth.ts
文件中,逻辑非常简单,如下:
import { atob } from "./base64";
// 在auth.js中定义设置和获取token的方法
export function getToken(accessOrRefreshKey: "accessToken" | "refreshToken"): string {
return uni.getStorageSync(accessOrRefreshKey);
}
export function setToken(accessOrRefreshKey: "accessToken" | "refreshToken", value: string) {
return uni.setStorageSync(accessOrRefreshKey, value);
}
// 清除双token
export function clearToken() {
uni.removeStorageSync("accessToken");
uni.removeStorageSync("refreshToken");
}
// 获取过期时间,token需要符合JWT格式且有exp属性
export function getExpireInPayload(token: string): number {
if(!token) return -1; // 所有时间戳都会大于-1,即没有token也算过期,做相应的过期处理,如跳转登录
const parts = token.split(".");
const payload = JSON.parse(atob(parts[1]));
return Number(payload.exp);
}
拦截器实现该策略
然后,我们就可以使用uniapp自带的request拦截器实现该双token策略,基本逻辑就和概述中描述的逻辑是一致的,可以参考着查看以下代码,main.ts
中:
let inRefresh = false;
// 请求拦截器
uni.addInterceptor("request", {
async invoke(request) {
uni.showLoading({ title: "正在请求中..." });
const meStore = useMeStore();
// meStore.inLogin是pinia中判断当前请求是否为登录请求
if (meStore.inLogin || inRefresh) return request;
const timestamp = Math.ceil( new Date().getTime() / 1000); //获取当前的时间戳
// 1. access部分
const accessToken = getToken("accessToken"); // 获取身份验证令牌
const expInAccessToken = getExpireInPayload(accessToken);
// accessToken未过期,直接加入请求头请求
if (timestamp < expInAccessToken) {
request.header.Authorization = `Bearer ${accessToken}`;
return request;
}
// 2. refresh部分
const refreshToken = getToken("refreshToken");
const expInRefreshToken = getExpireInPayload(refreshToken);
// refreshToken未过期,刷新Token
if (timestamp < expInRefreshToken) {
const { execute } = useMutation(refreshTokenGQL);
inRefresh = true; // 避免递归栈溢出
const { data, error } = await execute({ token: refreshToken });
inRefresh = false;
console.log("refresh data: ", data);
console.log("refresh error: ", error);
// save
const { accessToken: newAccessToken, refreshToken: newRefreshToken } = data?.refreshToken || {};
request.header.Authorization = `Bearer ${newAccessToken}`;
setToken("accessToken", newAccessToken);
setToken("refreshToken", newRefreshToken);
} else {
// refreshToken过期,需要重新登录
uni.reLaunch({
url: "/pages/me/index",
success: () => {
uni.showToast({
title: "登录凭证无效",
icon: "error",
duration: 2000,
});
},
});
}
return request;
},
fail(err) {
uni.showToast({
title: `网络请求错误`,
icon: "error",
duration: 2000,
});
},
complete() {
// showLoading需要每次请求前手动添加,因为里面有可自定义的title
uni.hideLoading();
},
});
最后
上述代码在该仓库中都全部存在,欢迎查看
这篇好文章是转载于:学新通技术网
- 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
- 本站站名: 学新通技术网
- 本文地址: /boutique/detail/tanhcefacg
-
photoshop保存的图片太大微信发不了怎么办
PHP中文网 06-15 -
《学习通》视频自动暂停处理方法
HelloWorld317 07-05 -
Android 11 保存文件到外部存储,并分享文件
Luke 10-12 -
word里面弄一个表格后上面的标题会跑到下面怎么办
PHP中文网 06-20 -
photoshop扩展功能面板显示灰色怎么办
PHP中文网 06-14 -
微信公众号没有声音提示怎么办
PHP中文网 03-31 -
excel下划线不显示怎么办
PHP中文网 06-23 -
excel打印预览压线压字怎么办
PHP中文网 06-22 -
TikTok加速器哪个好免费的TK加速器推荐
TK小达人 10-01 -
怎样阻止微信小程序自动打开
PHP中文网 06-13