目标:能够实现无感刷新 token 实现自动登录 分析说明:
概述:在登录超时或者 token 失效时,也就是服务器接口返回 401,通过 refresh_token 换取新的 token 过程如下(以获取个人资料数据为例):
【1】发送请求获取个人资料数据
【2】接口返回 401,也就是 token 失效了
【3】在响应拦截器中统一处理,换取新的 token
【4】将新的 token 存储到本地缓存中
【5】**继续发送获取个人资料的请求,完成数据获取** - 关键点
【6】如果整个过程中出现了任意异常,一般来说就是 refresh_token 也过期了,换取 token 失败,此时,要进行错误处理,也就是: 清除 token,跳转到登录页面
axios 请求拦截过程说明:
1 axios.get()
2 请求拦截器
3 响应代码
4 响应拦截器
以上 4 个步骤的执行顺序:
【1 axios.get()】 --> 【2 请求拦截器】>>> 服务器处理 >>> 【4 响应拦截器】 --> 【3 响应代码】
**步骤**:
1. 使用 try-catch 处理异常,出现异常时,清除 token,清空 redux token,跳转到登录页面
1. 判断本地存储中,是否有 `refresh_token`
1. 如果没有,直接跳转到登录页面,让用户登录即可
1. 如果有,就使用 `refresh_token` 通过 **axios 发送请求**,换取新的 token
1. 将新获取到的 token 存储到本地缓存中和 redux 中
1. 继续发送原来的请求
// 响应拦截器
http.interceptors.response.use(undefined, async (error) => {
// 响应失败时,会执行此处的回调函数
if (!error.response) {
// 网路超时
Toast.show({
content: '网络繁忙,请稍后再试',
duration: 1000,
});
return Promise.reject(error);
}
// 在此处,通过 refresh_token 来换取新的 token
if (error.response.status === 401) {
try {
// ...
// 先判断 redux 中是否有 refresh_token
const { refresh_token } = store.getState().login;
if (!refresh_token) {
// console.log('refresh_token 没有了')
// // 本地没有
// Toast.show({
// content: '登录超时,请重新登录',
// duration: 1000,
// afterClose: () => {
// customHistory.push('/login', {
// from: customHistory.location.pathname
// })
// }
// })
// return Promise.reject(error)
// 1 手动抛出异常
// throw new Error(error)
// 2 因为 try-catch 无法直接捕获 Promise 的异常,所以,此处
// 通过 await 等待 Promise 完成。然后,try-catch 就可以
// 捕获到该异常了
await Promise.reject(error);
}
// 有 refresh_token,就用 refresh_token 换取新的 token
// 注意:
// 1 此处需要使用 axios 发请求
// 2 因为使用的是 axios 所以,此处需要指定完整的 接口路径
// 3 对于 put 请求来来说,第 3 个参数才表示配置项,才能够设置 请求头
// 4 此处的请求头用的是 refresh_token 而不是 token
const res = await axios.put(`${baseURL}/authorizations`, null, {
headers: {
Authorization: `Bearer ${refresh_token}`,
},
});
// console.log(res)
// 使用新拿到的 token 替换本地的 token 以及 redux 中的 token
// 组装所有 token
const tokens = {
// token 是最新的,接口返回的
token: res.data.data.token,
// 因为接口没有返回新的 refresh_token,所以,需要使用原来的
refresh_token,
};
setToken(tokens);
store.dispatch({ type: 'login/token', payload: tokens });
// 继续完成原来要执行的操作
// 比如,在获取个人资料时,token 超时了,最终,在拿到最新的 token 后
// 要继续获取个人资料
// console.dir(error)
// 可以通过 error.config 来拿到原来发送的请求的相关信息
// 所以,要执行原来的操作,只需要将 error.config 重新请求一次即可
// 注意:此处,一定要返回 Promise 的结果
return http(error.config);
} catch (e) {
// 如果换取新 token 的过程中,代码出错了,一般就说明 refresh_token 失效了
// 此时,就清空token然后返回登录页面
// 注意:在直接分发 thunk action 时,会报类型错误
// store.dispatch(logout())
// 解决方式:先自己手动分发对象形式的 action 来实现退出
store.dispatch({ type: 'login/logout' });
// 手动清理本地的 token
clearToken();
Toast.show({
content: '登录超时,请重新登录',
duration: 1000,
afterClose: () => {
customHistory.push('/login', {
from: customHistory.location.pathname,
});
},
});
return Promise.reject(error);
}
}
});