赞
踩
在开发中为了安全或满足分布式场景,通常会舍弃原有的session认证手段,而采用jwt(json web token);但是使用token难免遇到token有效期的问题,如果token长期有效,服务端不断发布新的token,导致有效的token越来越多,这必然是存在安全问题的。而token不想session一样,在用户操作时会进行刷新,为了用户体验,这个刷新就需要自己实现。
如果采取单个token的方式要实现token的自动刷新,就必须使用定时器,每隔一段时间自动刷新token,并且这个时候token一定要是没有过期的,因为如果已经过期的token也可以用来刷新,这和长期有效的token也没什么不同。但这种方式存在一定的问题:
这里重点介绍这种方式,此方案的大致流程为:登录后客户端收到两个token(access_token,refresh_token),其中access_token用来鉴定身份,而refresh_token用来刷新access_token;这就要求了refresh_token要比access_token有效时间要长,并且refresh_token不能用来鉴定身份
使用这种方案又有一下解决方式:
再理一理程序运行的流程:
用到的工具函数
//判空
let isEmpty = function(obj) {
return obj == null || obj == "undefined" || obj == "null" || new String(obj).trim() == '';
};
const my_axios = axios.create({
baseURL: '/app',
timeout: 15000,
withCredentials: true
});
这里对axios做一个简单的封装,不做过多赘述
my_axios.interceptors.request.use( req => { //判断当前是否存在tokenBo(tokenBo即两个token组成的对象),存在则带上token //isEmpty函数在上方的工具函数中 if (!isEmpty(sessionStorage.getItem("tokenBo"))) { //通过请求路径中是否含有refershToken来判断当前是一般请求还是token刷新请求;从而在请求头中带上不同的token if (-req.url.indexOf("refreshToken") == 1) { req.headers['accessToken'] = JSON.parse(sessionStorage.getItem("tokenBo")).accessToken; } else { req.headers['refreshToken'] = JSON.parse(sessionStorage.getItem("tokenBo")).refreshToken; } } return req; }, err => { return Promise.reject(err) } )
//标志当前是否正在刷洗token let isNotRefreshing = true; //请求队列 let requests = []; my_axios.interceptors.response.use( async res => { //我们可以定义一个标准响应体,比如:{code=10415,msg='token已过期',data:null},当收到token过期的响应就要进行token刷新了 if (res.data.code == 10415) { //首先拿到响应的配置参数,这和请求的配置参数是一样的,包括了url、data等信息,待会需要使用这个config来进行重发 const config = res.config; //如果当前不处于刷新阶段就进行刷新操作 if (isNotRefreshing) { isNotRefreshing = false; //返回刷新token的回调的返回值,本来考虑到由于请求是异步的,所以return会先执行,导致返回一个undefined,那么就需要使用async+await,但实际上没有加也成功了 return my_axios.get("/admin/refreshToken") .then(res => { //如果token无效或token仍然过期,就只能重新登录了 if (res.code == 10422 || res.code == 10415) { sessionStorage.removeItem("tokenBo"); sessionStorage.removeItem("currentAdmin"); location.href = '/login'; } else if (res.code == 10200) { //刷新成功之后,将新的token存起来 sessionStorage.setItem("tokenBo", JSON.stringify(res.data)) //执行requests队列中的请求,(requests中存的不是请求参数,而是请求的Promise函数,这里直接拿来执行就好) requests.forEach(run => run()) //将请求队列置空 requests = [] //重新执行当前未执行成功的请求并返回 return my_axios(config); } }) .catch(() => { sessionStorage.removeItem("tokenBo"); sessionStorage.removeItem("currentAdmin"); location.href = '/'; }) .finally(() => { isNotRefreshing = true; }) } else { //如果当前已经是处于刷新token的状态,就将请求置于请求队列中,这个队列会在刷新token的回调中执行,由于new关键子存在声明提升,所以不用顾虑会有请求没有处理完的情况,这段添加请求的程序一定会在刷新token的回调执行之前执行的 return new Promise(resolve => { //这里加入的是一个promise的解析函数,将响应的config配置对应解析的请求函数存到requests中,等到刷新token回调后再执行 requests.push(() => { resolve(my_axios(config)); }) }) } } else { if (res.data.code == 10200) { requests = []; return res.data; } else { if (res.data.code == 10409) { sessionStorage.removeItem("tokenBo"); sessionStorage.removeItem("currentAdmin"); location.href = "/#/login" } Message.error(res.data.message); return res.data; } } }, err => { if (err && err.response && err.response.status) { switch (err.response.status) { case 404: Message.error("页面未找到"); break; case 401: Message.error('没有权限访问') break; case 500: Message.error("系统维护中") break; case 505: Message.error("网络错误") } } } )
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。