token 失效方案
- 在请求发起前拦截:判断
token的有效时间是否已经过期,若已过期,则将请求挂起,先刷新token后再继续请求
- 优点:节省请求,省流量
- 缺点:需要后端额外提供一个
token过期时间的字段;若使用了本地时间判断,当本地时间被篡改,特别是本地时间比服务器时间慢时,拦截会失败 - 方法:使用
axios.interceptors.request.use()处理请求前拦截
- 在响应返回后拦截:先发起请求,接口返回过期后,先刷新
token,再进行一次重试
- 优点:不需额外的
token过期字段,不需判断时间 - 缺点:会消耗多一次请求,耗流量
- 方法:使用
axios.interceptors.response.use()处理请求后拦截
- 如何防止多次刷新
token
- 问题:如果
refreshToken接口还没返回,此时再有一个过期的请求进来,就会再一次执行refreshToken,这就会导致多次执行刷新token的接口 - 解决:在 封装的请求文件
request.js中用一个flag来标记当前是否正在刷新token的状态,如果正在刷新则不再调用刷新token的接口
- 同时发起两个或以上的请求时,其他接口如何重试
- 问题:多个接口几乎同时发起和返回,第一个接口会进入刷新
token后重试的流程,而之后的接口需要先存起来,然后等刷新token后再重试 - 当第二个过期的请求进来,
token正在刷新,先将这个请求存到一个数组队列中,让其处于等待中,一直等到刷新token后再逐个重试清空请求队列 - 借助 Promise,让请求处于等待中
- 将请求存进队列中后,同时返回一个
Promise,让这个Promise一直处于Pending状态,只要不执行resolve,这个请求就会一直在等待。当刷新请求的接口返回后,再调用resolve,逐个重试。
- 将请求存进队列中后,同时返回一个
token 失效处理
在 layouts/default.vue 中的 mounted 阶段处理,重新刷新页面或者打开 APP 初次加载时会执行一次,内部路由切换时,不会再次执行,不会太影响页面性能。
- 是否存在 token
- 存在时请求 is-login 接口,通过接口返回判断 token 是否失效
- 未失效,走正常流程
- 失效时,通过 uuid 请求无身份信息的 token,并且清除 localStorage 缓存信息
- 不存在时,通过 uuid 获取无身份信息的 token
- 存在时请求 is-login 接口,通过接口返回判断 token 是否失效
// layouts/default.vue
<template>
<div>
<header-component />
<nuxt />
<menu-component />
</div>
</template>
<script>
export default {
data() {
return {};
},
mounted() {
// 设置token
this.setToken(() => {
this.showContent = true;
});
},
methods: {
setToken(callback) {
const token = this.$getCookie("token");
if (token) {
this.$axios.post("/api/is-login").then((res) => {
if (res.code === 2) {
// token失效
this.getUuid(async (uuid) => {
const res = await this.getToken(uuid);
if (res.code === 0) {
this.$setStorage("loginState", false);
this.$setCookie("token", res.response.token, 365);
callback();
}
});
} else {
// token正常
callback();
}
});
} else {
// 没有token
this.getUuid(async (uuid) => {
const res = await this.getToken(uuid);
if (res.code === 0) {
this.$setCookie("token", res.response.token, 365);
callback();
}
});
}
},
// 获取uuid
getUuid(callback) {
const appUuid = this.$getCookie("uuid");
if (appUuid) {
callback(appUuid);
} else {
const Fingerprint2 = require("fingerprintjs2");
Fingerprint2.getV18({}, (result) => {
this.$setCookie("uuid", result, 365);
callback(result);
});
}
},
},
};
</script>
<style lang="scss" scoped></style>
权限控制中间件
middleware/authorities.js文件:权限控制中间件
- 登陆页和活动页不需要登陆,可以直接进
- 需要强制登录的页面,判断是否存在 token,不存在时跳转到首页,存在时正常登录
// 获取客户端token
function getToken() {
return Cookies.get(TokenKey);
}
// 获取服务端token
function getTokenInServer(req) {
let serviceCookie = "";
if (req && req.ctx && req.ctx.cookies) {
serviceCookie = req.ctx.cookies.get(TokenKey);
}
return serviceCookie;
}
export default function ({ req, route, redirect, $axios }) {
const isLogin = route.name && route.name.indexOf("login") === 0; // 登录页不需验证
const isActivities = route.name && route.name.indexOf("activities") === 0; // 活动页不强制登录
const path = route.fullPath.split("?")[1]
? "?" + route.fullPath.split("?")[1]
: "";
const redirectURL = "/" + path;
const token = process.server ? getTokenInServer(req) : getToken();
// 注意要区分客户端和服务端,参数和处理会不一样
if (process.server) {
// 服务端渲染
if (!isLogin && !isActivities && !token) {
return redirect(redirectURL);
}
$axios.defaults.headers.common.Cookie = req.headers.cookie;
$axios.defaults.headers.common["user-agent"] = req.headers["user-agent"];
}
if (process.client) {
// 客户端渲染
if (!isLogin && !isActivities && !token) {
return redirect(redirectURL);
}
}
}