场景:线上平台有时会出现用户正在使用的时候,突然要用户去进行登录,这样会造成很不好的用户体验。
1.请求采用的是axios
2.平台的采用的 JWT(JSON Web Tokens)
进行用户登录鉴权。
原因:
1.突然跳转到登录页面,是由于当前的 token 过期,导致请求失败;在 axios
的响应拦截axiosInstance.interceptors.response.use
中处理失败请求返回的状态码 401,此时得知token
失效,因此跳转到登录页面,让用户重新进行登录。
2.平台目前的逻辑是在 token
未过期内,用户登录平台可直接进入首页,无需进行登录操作;因此就存在该现象:用户打开平台,由于此时 token
未过期,用户直接进入到了首页,进行其他操作。但是在用户操作的过程中,token
突然失效了,此时就会出现突然跳转到登录页面,严重影响用户的体验感!
操作:
后台提供了两个参数accessToken(用于请求头中,进行鉴权,存在有效期)和refreshToken
(刷新令牌,用于更新过期的 accessToken,相对于accessToken而言,他的时效期更长)
// 响应拦截
axiosInstance.interceptors.response.use(
(response) => {
return response;
},
(error) => {
let {
data, config
} = error.response;
return new Promise((resolve, reject) => {
/**
* 判断当前请求失败
* 是否由 toekn 失效导致的
*/
if (data.statusCode === 401) {
/**
* refreshToken 为封装的有关更新 token 的相关操作
*/
refreshToken(() => {
resolve(axiosInstance(config));
});
} else {
reject(error.response);
}
})
}
)
- 我们通过判断
statusCode
来确定,是否当前请求失败是由token
过期导致的; - 使用 Promise 处理将失败的请求,将由于
token
过期导致的失败请求存储起来(存储的是请求回调函数,resolve 状态)。理由:后续我们更新了token
后,可以将存储的失败请求重新发起,以此来达到用户无感的体验
封装 refreshToken 逻辑
- 存储由于
token
过期导致的失败的请求。 - 更新本地以及axios中头部的
token
。 - 当
refreshToken
刷新令牌也过期后,让用户重新登录。
// 存储由于 token 过期导致 失败的请求
let expiredRequestArr: any[] = [];
/**
* 存储当前因为 token 失效导致发送失败的请求
*/
const saveErrorRequest = (expiredRequest: () => any) => {
expiredRequestArr.push(expiredRequest);
}
// 避免频繁发送更新
let firstRequre = true;
/**
* 利用 refreshToken 更新当前使用的 token
*/
const updateTokenByRefreshToken = () => {
firstRequre = false;
axiosInstance.post(
'更新 token 的请求',
).then(res => {
let {
refreshToken, accessToken
} = res.data;
// 更新本地的token
localStorage.setItem('accessToken', accessToken);
// 更新请求头中的 token
setAxiosHeader(accessToken);
localStorage.setItem('refreshToken', refreshToken);
/**
* 当获取了最新的 refreshToken, accessToken 后
* 重新发起之前失败的请求
*/
expiredRequestArr.forEach(request => {
request();
})
expiredRequestArr = [];
}).catch(err => {
console.log('刷新 token 失败err', err);
/**
* 此时 refreshToken 也已经失效了
* 返回登录页,让用户重新进行登录操作
*/
window.location.href = `${HOME_PAGE}/login`;
})
}
/**
* 更新当前已过期的 token
* @param expiredRequest 回调函数,返回由token过期导致失败的请求
*/
export const refreshToken = (expiredRequest: () => any) => {
saveErrorRequest(expiredRequest);
if (firstRequre) {
updateTokenByRefreshToken();
}
}