前言
用户长时间未操作可能会引起token失效然后重新登陆的流程,为了不降低用户体验,所以要实现无感知的更新token。
实现思路
一般我们在用户登陆时,都会生成两个token。
token:用于用户正常验证。
refresh_token:更新(生成新)token时,需要验证refresh_token是否过期或被篡改。
当验证token时,发现token过期时返回自定义状态码
前端使用响应拦截器,根据自定义的状态码进行拦截。
使用refresh_token来重新生成token然后重发请求
代码部分
request.js
import axios from 'axios'
import store from '@/store'
import {
httpUrlbase
} from '@/utils/api.js'
import {
Message
} from 'element-ui'
// 创建axios实例
const service = axios.create({
baseURL: httpUrlbase, // url = base url + request url
timeout: 120000 // 请求超时设置
})
service.defaults.headers = {
'Content-Type': 'application/json;charset=UTF-8'
}
// 请求拦截器
service.interceptors.request.use(
config => {
//给请求头添加token
if (store.getters.token) {
config.headers['Token'] = store.getters.token
}
//给请求参数去除前后空格
if (config.method.toLowerCase() == 'post') {
for (let key in config.data) {
config.data[key] = typeof config.data[key] == 'string' ? config.data[key].trim() : config.data[key]
}
} else {
for (let key in config.params) {
config.params[key] = typeof config.params[key] == 'string' ? config.params[key].trim() : config.params[key]
}
}
return config
},
error => {
// 处理请求错误
return Promise.reject(error)
}
)
// 响应拦截
service.interceptors.response.use(
response => {
//从响应体里面结构出config(请求体)data(响应数据别名为res)
const {
config,
data: res,
} = response
// 如果自定义代码不是'10000',则判断为错误。
if (res.code && res.code + '' !== '10000') {
//code 40007 sub_code以'token-is-expired'结束code为业务状态码sub_code为具体错误状态码
if (res.code + '' == '40007' && (res.sub_code.endsWith('token-is-expired'))) {
return new Promise(function (resolve, reject) {
//post请求放到request body响应的时候是json字符串所以要转为json对象
if (config.method.toLowerCase() == 'post') {
config.data = JSON.parse(config.data)
}
//获取新的token
const verificationResult = await service({
url: "/api/v1/refresh_token",
method: "POST",
data: {
refresh_token:store.getters.refresh_token
}
})
if (verificationResult && verificationResult.code == "10000") {
store.commit("user/SET_TOKEN", verificationResult.data.token)//存储token
const newRes = await service(config) //重发请求
resolve(newRes);//将响应结果返回到业务层
}
})
} else {
Message(res.sub_message);//统一错误提示
}
} else {
return res
}
},
error => {
Message({
message: error.message,
type: 'error',
duration: 5 * 1000
})
return Promise.reject(error)
}
)
export default service
总结
用户请求成功直接把响应数据返回到业务层,当token失效时举个列子:
a发起请求
拦截器发现token失效进行刷新token和重发请求,把重发之后的响应结果返回到业务层
a得到正常响应因为拦截器处理了错误的请求把重发的结果返回了