文章目录
- 1. 需求
- 2. 解决思路
- 3. 代码实现
- 4. 其他
- 4.1 前置守卫
- 4.2 token存储方式
1. 需求
前端登录后,后端返回token
和refreshToken
,当token过期时要求用refreshToken
去获取新的token,前端需要做到无感知请求刷新token
。
2. 解决思路
当用户发起一个请求时,判断token是否已过期,若已过期则先调用refreshToken
接口,拿到新的token后再继续执行之前的请求。
方法:在响应拦截器中对请求返回结果进行拦截,判断token 返回过期后,调用刷新token接口,然后再重新进行一次请求。
难点:当同时发起多个请求,而刷新token的接口还没返回,此时其他请求该如何处理?
优化:
- 如何防止多次刷新token
如果refreshToken
接口还没返回,此时又有一个过期的请求进来,上面的代码就会再一次执行refreshToken
,这就会导致多次执行刷新token的接口,因此需要防止这个问题。
我们可以在中用一个
flag
来标记当前是否正在刷新token的状态,如果正在刷新则不再调用刷新token的接口。
- 同时发起两个或以上的请求时,其他接口如何重试
两个接口几乎同时发起和返回,第一个接口会进入刷新token后重试的流程,而第二个接口需要先存起来,然后等刷新token后再重试。同样,如果同时发起三个请求,此时需要缓存后两个接口,等刷新token后再重试。由于接口都是异步的,处理起来会有点麻烦。
当第二个过期的请求进来,token正在刷新,我们先将这个请求存到一个数组队列中,想办法让这个请求处于等待中,一直等到刷新token后再逐个重试清空请求队列。
为了解决这个问题,我们得借助Promise
。将请求存进队列中后,同时返回一个Promise
,让这个Promise
一直处于Pending
状态(即不调用resolve),此时这个请求就会一直等啊等,只要我们不执行resolve,这个请求就会一直在等待。当刷新请求的接口返回来后,我们再调用resolve,逐个重试。
3. 代码实现
- src/utils/
// 创建axios实例
const service = axios.create({
// axios中请求配置有baseURL选项,表示请求URL公共部分
// baseURL: .VUE_APP_BASE_API,
baseURL: '/api',
// 超时
timeout: 10000
})
// request拦截器
service.interceptors.request.use(config => {
const token = getToken()
if (token) {
config.headers['Authorization'] = 'Bearer ' + token // 让每个请求携带自定义token 请根据实际情况自行修改
}
return config;
}, error => {
console.log(error)
})
// 重试队列,每一项将是一个待执行的函数形式
let requests = []
// 响应拦截器
service.interceptors.response.use(res => {
if (res.data.status === 10) {
// 获取当前失败的请求
const config = res.config;
// 判断当前是否正在刷新token
if (GlobalVariable.refreshFlag === 0) {
GlobalVariable.refreshFlag = 1;
// 刷新token
return refreshToken().then(res => {
const token = res.data.access_token
// 在session中记录token信息
setToken(token)
setRefreshToken(res.data.refresh_token)
setScope(res.data.scope)
// 将token信息传递给后端(如果refreshToken是后端进行操作的,那么token已经保存在后端,就无需传递了)
setUserToken(res.data)
// 已经刷新了token,将所有队列中的请求进行重试
requests.forEach(cb => cb(token))
// 重试完了清空这个队列
requests = []
return service(config)
}).finally(() => {
GlobalVariable.refreshFlag = 0;
})
} else {
// 正在刷新token,将返回一个未执行resolve的promise
return new Promise((resolve) => {
// 将resolve放进队列,用一个函数形式来保存,等token刷新后直接执行
requests.push(() => {
resolve(service(config))
})
})
}
}
console.log(res)
return res;
}, err => {
console.log(err);
return Promise.reject(error)
})
以上已经成功解决无感知刷新token问题。
4. 其他
4.1 前置守卫
记录下前置守卫的写法
- src/
// 获取url地址栏中的参数
function GetQueryString(name) {
let reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
let r = window.location.search.substr(1).match(reg);
if (r != null) return unescape(r[2]);
return null;
}
// 前置守卫(gard)
// to 将要访问的路径
// from 从哪个路径跳转而来
// next是一个函数 表示放行
// next() 放行 next('/login') 强制跳转到login
// 从from跳转到to
router.beforeEach((to, from, next) => {
if (getToken()) {
/* has token*/
next() // 直接跳转
} else {
// 没有token
let code = GetQueryString("code");
if (code) {
request({
url: '/oauth/token',
params: {
grant_type: 'authorization_code',
code: code,
client_id: GlobalVariable.appClientId,
client_secret: GlobalVariable.clientSecret,
redirect_uri: GlobalVariable.redirectUri,
},
}).then((res) => {
setToken(res.data.access_token)
setRefreshToken(res.data.refresh_token)
setScope(res.data.scope)
setUserToken(res.data).then(() => {
getInfo().then(res => {
localStorage.setItem("ms_username", res.data.data.login_name)
getUserInfo(res.data.data.login_name).then(res => {
localStorage.setItem("regionId", res.data.data.regionId)
localStorage.setItem("deptId", res.data.data.deptId)
localStorage.setItem("deptLevel", res.data.data.deptLevel)
window.location.href = GlobalVariable.redirectUri
})
})
})
})
} else {
alert("请从用户中心进入!")
window.location.href = GlobalVariable.loginUrl
}
}
NProgress.done()
})
4.2 token存储方式
存储token有Cookie、 LocalStorage 、 SessionStorage三种方式
三者的异同
特性 | Cookie | localStorage | sessionStorage |
数据的生命期 | 一般由服务器生成,可设置失效时间。如果在浏览器端生成Cookie,默认是关闭浏览器后失效 | 除非被清除,否则永久保存 | 仅在当前会话下有效,关闭页面或浏览器后被清除 |
存放数据大小 | 4K左右 | 一般为5MB | |
与服务器端通信 | 每次都会携带在HTTP头中,如果使用cookie保存过多数据会带来性能问题 | 仅在客户端(即浏览器)中保存,不参与和服务器的通信 | |
易用性 | 需要程序员自己封装,源生的Cookie接口不友好 | 源生接口可以接受,亦可再次封装来对Object和Array有更好的支持 |
相同点:三者都可以被用来在浏览器端存储数据,而且都是字符串类型的键值对。
参考:
- axios如何利用promise无痛刷新token
- JS 详解 Cookie、 LocalStorage 与 SessionStorage