Refresh token 用来在 access token 过期后去获取新的 access token,refresh token 过期时间相对 access token 较长
发起请求 -> 接口返回 access token 已过期状态码 -> 用之前存储的 refresh token 发起获取新的 access token 的请求 -> 拿到新的 access token 和 refresh token,用新的 access token 和 refresh token 替换旧的 access token 和 refresh token
-
在 access token 过期,用 refresh token 获取新的 access token 期间可能会有多个请求已经发出,当 access token 获取成功之后需要将这些请求重新发送出去
-
refresh token 只能使用一次,因此需要在使用 refresh token 获取 access token 时添加一个标识,标记正在获取 access token,避免其他过期的接口重复发起请求
const request = axios.create({})
// 封装重新获取 token 方法
function refreshToken () {
return axios.create()({
method: 'POST',
url: '/refresh_token',
data: qs.stringify({
refreshtoken: store.state.user.refresh_token
})
})
}
// 请求拦截器
request.interceptors.request.use(function (config) {
const { user } = store.state
if (user && user.access_token) {
config.headers.Authorization = user.access_token
}
// 注意:这里一定要返回 config,否则请求就发不出去了
return config
}, function (error) {
return Promise.reject(error)
})
let isRfreshing = false // 控制刷新 token 的状态
let requests: any[] = [] // 存储刷新 token 期间过来的 401 请求
// 响应拦截器
request.interceptors.response.use(function (response) {
// 状态码为 2xx 都会进入这里
// 如果是自定义错误状态码,错误处理就写到这里
return response
}, async function (error) {
// 超出 2xx 状态码都都执行这里
// 如果是使用的 HTTP 状态码,错误处理就写到这里
if (error.response) {
// 请求发出去收到响应了,但是状态码超出了 2xx 范围
const { status } = error.response
if (status === 400) {
Message.error('请求参数错误')
} else if (status === 401) {
// token 无效(没有提供 token、token 是无效的、token 过期了)
// 如果有 refresh_token 则尝试使用 refresh_token 获取新的 access_token
if (!store.state.user) {
redirectLogin()
return Promise.reject(error)
}
// 刷新 token
if (!isRfreshing) {
isRfreshing = true // 开启刷新状态
// 尝试刷新获取新的 token
return refreshToken().then(res => {
if (!res.data.success) {
throw new Error('刷新 Token 失败')
}
// 刷新 token 成功了
store.commit('setUser', res.data.content)
// 把 requests 队列中的请求重新发出去
requests.forEach(cb => cb())
// 重置 requests 数组
requests = []
return request(error.config)
}).catch(err => {
console.log(err)
store.commit('setUser', null)
redirectLogin()
return Promise.reject(error)
}).finally(() => {
isRfreshing = false // 重置刷新状态
})
}
// 刷新状态下,把请求挂起放到 requests 数组中
// 当 token 刷新成功后,调用 requests 数组内存储的方法,resolve 重新发起的请求
// 重新发出的请求成功后,外层的 Promise 就可以拿到接口重新请求返回的值
return new Promise(resolve => {
requests.push(() => {
resolve(request(error.config))
})
})
} else if (status === 403) {
Message.error('没有权限,请联系管理员')
} else if (status === 404) {
Message.error('请求资源不存在')
} else if (status >= 500) {
Message.error('服务端错误,请联系管理员')
}
} else if (error.request) {
// 请求发出去没有收到响应
Message.error('请求超时,请刷新重试')
} else {
// 在设置请求时发生了一些事情,触发了一个错误
Message.error(`请求失败:${error.message}`)
}
// 把请求失败的错误对象继续抛出,扔给上一个调用者
return Promise.reject(error)
})