无感刷新的基本原理
- 使用刷新令牌(refresh token):
○ 应用程序在首次登录成功后会获得一个访问令牌(access token)和一个刷新令牌(refresh token)。
○ 访问令牌通常有较短的有效期,而刷新令牌的有效期较长。 - 自动刷新:
○ 当访问令牌即将过期时,应用程序会在后台使用刷新令牌来获取新的访问令牌。
○ 这个过程通常是透明的,用户不会察觉到任何变化。
业务场景及双token的处理流程图
- 请求登录接口拿到两个token,在响应拦截器中存储在本地
javascript
service.interceptors.response.use(
async function (response) {
// 对响应数据做点什么
if (response.data.code === 0) {
response.data.data.token &&
localStorage.setItem("token", response.data.data.token);
response.data.data.refresh_token &&
localStorage.setItem("refresh_token", response.data.data.refresh_token);
return response.data.data;
}
return response;
},
function (error) {
// 对响应错误做点什么
return Promise.reject(error);
}
);
- 提交业务表单时候,请求拦截器中请求头携带上短token
javascript
service.interceptors.request.use(
function (config) {
// 在发送请求之前做些什么
// token
if (config.url !== "/login") {
config.headers["Authorization"] = `Bearer ${localStorage.getItem(
"token"
)}`;
}
return config;
},
function (error) {
// 对请求错误做些什么
return Promise.reject(error);
}
);
vue
const onSubmit = ()=>{
postFrom().then(res=>{
if(res.content){
ElMessage.success('提交成功')
dialogVisible.value = false
}else{
//token失效,跳转登录页
router.push('/login')
}
})
}
- 如果此时短token失效
- 通过请求getRefresh()接口获取新的token
在发起请求之前,需要在请求拦截器中设置携带长token
javascript
service.interceptors.request.use(
function (config) {
if (config.url === "/refresh_token") {
config.headers["Authorization"] = `Bearer ${localStorage.getItem(
"refresh_token"
)}`;
}
return config;
},
);
javascript
service.interceptors.response.use(
async function (response) {
// 对响应数据做点什么
if (response.data.code === 0) {
......
} else if (response.data.code === 401) {
console.log('token过期');
// 请求刷新token
const result = await getRefresh();
console.log(result);
}
return response;
},
);
- 获取到新的token之后,设置请求头携带新token,再次发起提交表单
javascript
if (result.token) {
console.log('getRefresh()携带长token去重新获取短token', result);
//拿到新的token之后,设置或更新请求头中的 Authorization 字段。
response.config.headers.Authorization = `Bearer ${localStorage.getItem("token")}`;
// 再次发起提交表单的接口/protected
return service.request(response.config);
}
此时已经实现,无感刷新token
- 假设长token也失效了,则跳转登录页重新获取 长短新token
javascript
let isRefreshing = false; //标记是否正在刷新token
// 添加响应拦截器
service.interceptors.response.use(
async function (response) {
// 对响应数据做点什么
if (response.data.code === 0) {
response.data.data.token &&
localStorage.setItem("token", response.data.data.token);
response.data.data.refresh_token &&
localStorage.setItem("refresh_token", response.data.data.refresh_token);
return response.data.data;
} else if (response.data.code === 401) {
if (!response.config._retry && !isRefreshing ) {
console.log('token过期');
isRefreshing = true; // 标记为正在刷新token
response.config._retry = true;
// 请求刷新token
const result = await getRefresh();
console.log(result);
//之所以不需要再次设置新的短token,是因为getRefresh()接口的状态码是0,所以在上面已经设置过新token
if (result.token) {
console.log('getRefresh()携带长token去重新获取短token', result);
//拿到新的token之后,设置或更新请求头中的 Authorization 字段。
response.config.headers.Authorization = `Bearer ${localStorage.getItem("token")}`;
isRefreshing = false;
// 再次发起提交表单的接口/protected
return service.request(response.config);
}
} else if (isRefreshing) { // 已经在刷新,不再重复刷新
console.log('长token也过期了,跳转登录页');
isRefreshing = false;
ElMessage.error('请重新登录');
}
}
return response;
},
function (error) {
// 对响应错误做点什么
return Promise.reject(error);
}
);
完整代码