携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第3天,点击查看活动详情
前言
上片文章写到了 axios基础版封装 ,已经基本可以满足日常的开发需求了,但是!这就够了吗?登录功能在系统中最常见不过了,此时产品走了过来说道,咱们要加一个需求,目前咱们的token时间有些短,token过期就要重新去登录,用户此时需要频繁的去登录,这种体验非常不好。 我心想直接把token时间设置的长一点就行了,甩给后端就行,但是经过后端同学的上课后才知道我的想法low到极致。
经过了解后端设置时间太长会存在安全问题,避免安全问题所以还是需要前端配合完成
实现思路
需要两个token存储(过期)时间不同。短token用来请求应用数据,长token用于获取新的短token。后端提供一个获取短token的接口,我们在axios响应拦截的时候去判断短token(应用数据)有没有过期,若过期利用长token去请求接口拿到新的短token,进行重新赋值,实现token无感的刷新,若长token过期则需要用户重新登录(安全烤考虑)。
const ACCESS_TOKEN = "s_tk"
const REFRESH_TOKEN = "l_tk"
需要和后端约定token过期的code值,方便判断和排查
//code.js
// 需要重新登陆
export const CODE_RELOGIN = 410;
// 应用数据token过期
export const CODE_TOKEN_EXPIRED = 411;
//接口请求成功
export const CODE_SUCCESS = 0;
定义一些关于token的常量存储
//token-info.js 常量
export const ACCESS_TOKEN = "s_token" //短token
export const REFRESH_TOKEN = "l_token"; //长token
export const AUTH = "Authorization";
export const REFRESH = "REFRESH";
实现
依旧去创建axios实例,在请求拦截和请求响应中分别去做一些事情。
import axios from "axios";
import { ACCESS_TOKEN, AUTH } from "./token-info";
import { CODE_RELOGIN, CODE_TOKEN_EXPIRED, CODE_SUCCESS } from "./code"
import { refreshAccessToken, NoneTokenRequestList } from "./refresh"
const service = axios.create({
baseURL:'',
timeout:10000,
withCredentials:false, //跨域请求是否要携带cookie
})
// 请求拦截
service.interceptors.request.use(
(config) => {
const s_tk = localStorage.getItem(ACCESS_TOKEN);
s_tk && config.headers[AUTH]
return config;
},
(error) => {
return Promise.reject(error);
}
);
// 响应拦截
service.interceptors.response.use(
response => {
let { config, data} = response
const code = Number(res.data.code) || 200
return new Promise((resolve, reject) => {
if(code === CODE_TOKEN_EXPIRED) { //说明token过期了
// ...做点事情吧,为了不侵入业务代码,抽取两个函数进行处理
NoneTokenRequestList(() => resolve(service(config)))
refreshAccessToken()
}else {
return reject(data)
}
})
},
(error) => {
return Promise.reject(error)
}
)
export default service
在services文件下新建一个refresh.js文件用来处理token逻辑,这里我们考虑两个问题
-
防止多次刷新token
第一个请求过期去请求刷新token的接口,此时又有一个接口刚好过期,那么也要去刷新token,获取新token的接口被重复请求了两次,这已经造成了性能浪费
-
同时两个以上的请求时,其他接口如何重试
当第一个请求过期去刷新token的时候,我们希望后面的接口能够在等待ing,可以利用队列去把后面的请求存进去,队列先进先出,利用promise让队列中的排队的请求处于等待中
具体代码如下:
import service from "./http";
import { ACCESS_TOKEN, REFRESH_TOKEN, REFRESH } from "./token-info";
let subscribers = []
let isRefreshing = false
export const NoneTokenRequestList = (request) => {
subscribers.push(request)
}
export const retryRequest = () => {
subscribers.forEach((request) => request())
subscribers = []
}
export const refreshAccessToken = async () => {
if(!isRefreshing) {
try {
isRefreshing = true
const l_token = localStorage.getItem(REFRESH_TOKEN);
if (l_token) {
/* 利用长token重新获取短token */
const { accessToken } = await service.get(
"/请求新的token的接口",
{ headers: { [REFRESH]: l_token } }
);
localStorage.setItem(ACCESS_TOKEN, accessToken);
retryRequest()
}
return;
} catch (err) {
clearAuthAndRedirect()
return
} finally {
isRefreshing = false
}
}
}
/* 清除长短token,并定位到登录页(在项目中使用路由跳转) */
export const clearAuthAndRedirect = () =>{
localStorage.removeItem(ACCESS_TOKEN)
window.location.href = '/login'
}
感谢大佬的文章指引:
封装 axios 拦截器实现用户无感刷新 access_token
总结
作为一个刚刚毕业的前端小白,希望在这里可以记录自己的点滴成长,开拓眼界,养成习惯!