管理系统登录token小结
登录时借助vuex和localStorage进行登录态的存储和操作
1.管理系统点击登录按钮,登陆成功后跳转至内部后台页面,需要设置路由导航守卫防止用户通过地址栏输入路由地址的方式跳过登录直接进入系统后台,即有门无墙
注意:不一定登陆成功后进入后台就是首页,可通过query方式进行传参(未尝试params,理论上可行且原理相同),保存用户被导航守卫拦截的页面,登陆成功后进入被拦截的页面,以模拟用户通过书签进入系统的场景
// 导航守卫;
router.beforeEach((to, from, next) => {
//matched数组中只要有一个需要验证就进行验证
//matched说明:/home/user/mobile ,这个路径中,matched会匹配到'/home','/user','/mobile'
if (to.matched.some((record) => record.meta.requiresAuth)) {
//验证vuex中登录信息user是否存在
if (!store.state.user) {
//未登录,跳转至登录页
console.log("跳转login");
return next({
name: "login",
query: {
// 将本次路由的fullpath传递给login页面,fullpath相比path,会包含页面的请求参数等等信息
redirect: to.fullPath,
},
});
}
next();
} else {
next();
}
});
2.处理重复请求问题 2.1重复请求登录接口问题
为防止用户登录时频繁点击登录按钮导致重复发送登录请求且进入后台页面后弹出多个提示窗口,可通过elementUI button的loading属性登录token无效,在点击后将loading绑定的属性值置为true,使得按钮被禁用
2.2 重复请求token刷新问题!!!
服务端返回的登录token一般都会设置有expires,即token的有效时间,当token过期时,用户需要重新登录。但这样往往是有问题的,若用户在token即将过期时在后台系统内进行操作,token过期让用户跳转至登陆页面,用户体验差且可能会丢失一些重要数据的操作,因此这是我们需要进行无感刷新
token过期后需要更新token,但并不需要每一个接口都调用一遍token刷新接口,只要有一个接口调用一边将token更新即可,因此需要一个标识,当有接口正在刷新token时,其他接口就没必要再继续发送刷新请求。
但此时又出现了另一个问题,因token过期而被挂起的请求登录token无效,在刷新token后需要重新调用一次,但因为刷新的标识导致只有请求了刷新token接口的请求再刷新了token后被再次调用,因此还需要一个数组,来记录因为token刷新而被挂起的请求。
在记录被挂起的请求数组时,采用了一种比较巧妙地方法,直接push一个函数,函数内部书写了接口的重新请求,这样在刷新token后,遍历调用一遍被挂起的请求数组即可
request.js文件
const request = axios.create({
timeout:2000
})
//存储是否正在更新token的状态
let isRefreshing = false;
//存储因为的等待token刷新而挂起的请求
let requestArr = [];
// 响应拦截器
request.interceptors.response.use(
function (response) {
// 状态码2xx 响应成功
return response;
},
//错误处理
function (error) {
// 响应失败
if (error.response) {
//请求发送成功,响应接收完毕,但状态码为失败
let { status } = error.response;
let errorMessage = "";
if (status === 400) {
errorMessage = "请求参数错误";
} else if (status === 401) {
// 无感刷新,不需要用户看到token过期,无需定义errorMeassage
//1.无token信息
if (!store.state.user) {
router.push({
name: "login",
query: {
//router.currentRoute就是存储了路由信息的对象
redirect: router.currentRoute.fullPath,
},
});
return Promise.reject(error);
}
//检测是否已经存在了刷新token的请求
if (isRefreshing) {
//将当前因为token刷新被挂起的请求,存储到请求列表中
//向请求挂起列表中推入一个函数,函数内部为本次失败请求的重新发送,调用传入的函数,本次请求就会被重新发送
requestArr.push(() => {
request(error.config);
});
return;
}
isRefreshing = true;
//2.token无效(错误或无效)
//发送请求,获取新的token
return request({
method: "POST",
url: "/xxx/xxx/refreshtoken",
//qs urlencoded
data: qs.stringify({
refreshtoken: store.state.user.refresh_token,
}),
}).then((res) => {
//刷新token失败
if (res.data.state !== 1) {
// 如果登录信息无效,清除无效信息,并跳转登录页重新登陆
store.commit("SETUSER", null);
router.push({
name: "login",
query: {
redirect: router.currentRoute.fullPath,
},
});
return Promise.reject(error);
}
//刷新token成功
store.commit("SETUSER", res.data.content);
// 重新发送失败的请求,error.config即本次失败请求的配置对象,根据requestArr重新发送本次因为token刷新而挂起的所有请求
requestArr.forEach((item) => item());
//被挂起的请求都已经重新发送,置空挂起请求列表
requestArr = [];
//重新发送本次请求,本次请求因为isRefreshing=false,并不在被挂起的请求列表中
return request(error.config);
}).catch((err) => {
console.log("err", err);
}).finally(() => {
// 请求发送完毕,响应处理完毕,无论是否刷新token成功,都改变是否正在刷新token的状态,以便下一次使用
isRefreshing = false;
});
} else if (status === 403) {
errorMessage = "没有权限,请联系管理员";
} else if (status === 404) {
errorMessage = "请求资源不存在";
} else if (status === 500) {
errorMessage = "服务器错误,请联系管理员";
}
Message.error("errorMessage", errorMessage);
} else if (error.request) {
//请求发送成功,但未收到响应
// The request was made but no response was received
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
// http.ClientRequest in node.js
// Message.error(error.request);
Message.error("请求超时,请重试");
} else {
//意料之外的错误
// Something happened in setting up the request that triggered an Error
Message.error(error.message);
}
//将拦截器拦截的错误继续向后抛出,在错误抛出的位置通过try catch进行处理,而不是在拦截器中进行错误的处理
return Promise.reject(error);
}
);
3.接口鉴权问题
即并非所有的接口都可以直接请求,例如查看用户信息,需要用户登陆后才可以查看登录信息,若只传入接口文档需求参数,会报错401 UnAuthorized,因此需要在请求拦截器中向请求头config.headers塞入登录态token
注:没有token和token过期都会报错401未授权
2023/2/9 补充:接口鉴权可以编写白名单数组来规定哪些接口不需要token鉴权
未完,待补充。。。