最近公司的项目出现一些调整,登录状态的token,从原来存放在sessionStorage里,时效周期性和浏览器tab关联,改变为有效期变为8小时,而且在token失效时不跳转登录页面,而是自动刷新。因此为了实现无感刷新token,梳理了下大致流程
- 后端调整token接口逻辑,重新返回token数据格式
//token
{
access_token:'xxxxxx',
refresh_token:'xxxxx'
}
- access_token是业务代码token的验证标识,一般放在headers的Authorization里,需要拼接 'Bearer',如 'Bearer access_token'
- access_token失效之后,状态码返回401,前端根据401,用refresh_token请求刷新token的接口,获取token信息之后替换之后的Authorization
- 重新获取token之后,之前的api请求,继续执行
- 因为页面可能存在多个请求,需要解决因多个请求token失效而造成的重复刷新token接口
- refresh_token失效之后跳转login页面
以上几点就是无感刷新的大致逻辑,代码如下
封装请求接口request
import qs from 'query-string';
const NOT_TOKEN_API = [
'/oauth2/token',
]
/**
* request 封装fetch函数
* @params url 接口api
* @params options fetch请求参数
* * method GET,POST,PUT,DELETE
* * headers 请求头参数配置
* * body 请求体参数
* * __isRefresh 是否是刷新token的标识
*
*/
async function request(url, options) {
const {
method = 'GET',
headers = {
'Content-Type': 'application/json',
'Authorization':'Basic anR4LWx1bmFyOjEyMzQ1Ng==',
},
body = null,
__isRefresh=false
} = options||{}
const {access_token} = getToken()
// 非token接口,headers需要添加Authorization
//或者其他业务逻辑
if(NOT_TOKEN_API.findIndex(s=>url.includes(s))===-1) {
if(access_token) {
headers['Authorization'] = 'Bearer ' + access_token
}else {
message.error('token 丢失请重新登陆')
message.error('正在跳转登录页')
window.location.href = '/login'
}
}
// 构建请求配置
const requestOptions = {
method,
headers,
__isRefresh,
body: body ? JSON.stringify(body) : null,
};
// 发起请求并返回 Promise 对象
return fetch(url, requestOptions)
.then(async(response)=>{
const {status} = response
const res = await response.json()
if(status ===200) {
// 返回业务数据
return res;
}
//status为401时,token失效
//__isRefresh为false,重新刷新token,也就是请求resfreshtoken()
else if(status ===401 && !requestOptions.__isRefresh) {
const refresh = await refreshToken()
await sleep(100)
if(refresh) {
const resp = await request(url,options)
return resp
}
}else {
res && message.error(res.message||res.error_description||res.error)
return res||{}
}
})
.catch(async(error) => {
// 错误处理逻辑
message.error('请求失败')
return {error:'error'}
})
}
refreshToken刷新token
let promise = null
async function refreshToken () {
const {access_token,refresh_token} = getToken()
if(!access_token) {
return null
}
//通过请求实例唯一解决多个请求token失效时重复请求
if(promise) {
return promise
}
// 非token接口,headers需要添加Authorization
const params = {
grant_type: 'refresh_token',
refresh_token: refresh_token
}
// 序列化请求参数
const query = qs.stringify(params)
promise = new Promise(async(resp)=>{
await request(
`/api/auth/oauth2/token?${query}`,
{
method:'POST',
__isRefresh:true
}).then(async(res)=>{
// 设置
if(res?.access_token) {
localStorage.setItem('tokenInfo',JSON.stringify(res))
resp(res)
}else {
//refresh_token失效之后跳转login页面
message.error('正在跳转 login page')
goLogin()
resp(null)
}
})
})
// promise结束之后重置实例
promise.finally(()=>{
promise=null
})
return promise
}
const getToken = () => {
const tokenInfo = JSON.parse(localStorage.getItem('tokenInfo')||'{}')
return tokenInfo
}
const goLogin = () => {
localStorage.setItem('tokenInfo',JSON.stringify({}))
window.location.href="/login"
}
无感刷新token实现之后,如下图