简介
目前很多项目都使用的是token机制,考虑到安全问题,一般会返回刷新token 和 使用的token、token过期时间
解决思路
在网上看的资料,感觉思路都大致相同,分两种情况
- 1.在请求拦截中,拦截刷新token时,将config配置缓存,刷新成功后重新发起请求
- 2.在响应拦截中,当token快过期的时候,将接口缓存,token成功刷新后再发送请求
我们缓存接口就需要用到promise了,axios拦截器内部其实是用Promise.then来获取我们拦截器中返回的数据。平常我们在请求拦截器中会return一个config,但其实return一个Promise.resolve(config)也是可以的。
不了解promise的朋友可以看阮一峰老师的文章 es6.ruanyifeng.com/#docs/promi…
代码大致如下
第一种方法(请求拦截)
state: {
token: getToken(),
refresh_token: '',
expires_time: '' // token保质期
},
mutations: {
SET_TOKEN: (state, token) => {
state.token = token
},
SET_REFRESH_TOKEN: (state, data) => { // 保存延续token
state.refresh_token = data
},
SET_EXPIRES_TIME (state, data) { // 保存token过期时间
let NOW_DATE = parseInt(new Date().getTime() / 1000)// 保存当前登陆时间
state.expires_time = data + NOW_DATE
}
},
actions: {
loginIn ({ commit, state }, userInfo) {
commit('SET_TOKEN', userInfo.access_token)
commit('SET_REFRESH_TOKEN', userInfo.refresh_token)// 保存延续token
commit('SET_EXPIRES_TIME', userInfo.expires_in)// 保存token过期时间
Cookies.set('access_token', userInfo.access_token)
}
}
axios拦截器中
const isTokenExpired = () => { // 验证当前token是否过期
let expireTime = store.state.user.expires_time
if (expireTime) {
let nowTime = parseInt(new Date().getTime() / 1000)
let willExpired = (expireTime - nowTime) < 60// 如果超过60秒重新获取token
return willExpired
}
return false
}
// 是否正在刷新的标记 -- 防止重复发出刷新token接口--节流阀
let isRefreshing = false
// 失效后同时发送请求的容器 -- 缓存接口
let subscribers = []
// 刷新 token 后, 将缓存的接口重新请求一次
function onAccessTokenFetched (newToken) {
subscribers.forEach((callback) => {
callback(newToken)
})
// 清空缓存接口
subscribers = []
}
// 添加缓存接口
function addSubscriber (callback) {
subscribers.push(callback)
}
service.interceptors.request.use(
config => {
config.headers = {
'X-Requested-With': 'XMLHttpRequest',
'Accept': 'application/json',
'Authorization': 'Basic ' + cloudConfig.Authorization,
'Content-Type': 'application/json; charset=UTF-8'
}
if (store.getters.token) {
config.headers['Authorization'] = 'Bearer ' + getToken()
}
if (isTokenExpired() && !config.url.includes('/oauth/token')) { // 如果token快过期了
if (!isRefreshing) { // 控制重复获取token
isRefreshing = true
let formData = new FormData()
formData.append('grant_type', 'refresh_token')
formData.append('refresh_token', store.state.user.refresh_token)
axios({
url: '/identity/oauth/token',
method: 'post',
data: formData,
baseURL: cloudConfig.api_path,
timeout: cloudConfig.api_timeout,
headers: {
'Authorization': 'Basic ' + cloudConfig.Authorization
}
}).then(res => {
isRefreshing = false
// console.log(res)
if (res.status === 200) {
store.dispatch('loginIn', res.data)
onAccessTokenFetched(res.data.access_token)
}
}).catch(() => {
router.push({ path: '/login' })// 失败就跳转登陆
isRefreshing = false
})
}
// 将其他接口缓存起来 -- 这个Promise函数很关键
const retryRequest = new Promise((resolve) => {
// 这里是将其他接口缓存起来的关键, 返回Promise并且让其状态一直为等待状态,
// 只有当token刷新成功后, 就会调用通过addSubscriber函数添加的缓存接口,
// 此时, Promise的状态就会变成resolve
addSubscriber((newToken) => {
// 表示用新的token去替换掉原来的token
config.headers.Authorization = 'Bearer ' + newToken
// 替换掉url -- 因为baseURL会扩展请求url
config.url = config.url.replace(config.baseURL, '')
// 返回重新封装的config, 就会将新配置去发送请求
resolve(config)
})
})
return retryRequest
}
return config
},
error => {
console.log(error) // for debug
return Promise.reject(error)
}
)
service.interceptors.response.use(
response => {
const res = response.data
-------忽略以下代码
)
第二种方法(响应拦截)
其实和第一种方法大致差不多,只需要将retryRequest函数返回值改成axios
axios拦截器中
// 是否正在刷新的标记 -- 防止重复发出刷新token接口--节流阀
let isRefreshing = false
// 失效后同时发送请求的容器 -- 缓存接口
let subscribers = []
// 刷新 token 后, 将缓存的接口重新请求一次
function onAccessTokenFetched (newToken) {
subscribers.forEach((callback) => {
callback(newToken)
})
// 清空缓存接口
subscribers = []
}
// 添加缓存接口
function addSubscriber (callback) {
subscribers.push(callback)
}
service.interceptors.request.use(
config => {
config.headers = {
'X-Requested-With': 'XMLHttpRequest',
'Accept': 'application/json',
'Authorization': 'Basic ' + cloudConfig.Authorization,
'Content-Type': 'application/json; charset=UTF-8'
}
if (store.getters.token) {
config.headers['Authorization'] = 'Bearer ' + getToken()
}
return config
},
error => {
console.log(error) // for debug
return Promise.reject(error)
}
)
service.interceptors.response.use(
response => {
const res = response.data
if ((isTokenExpired() || response.responseStatus === 401) && !response.config.url.includes('/oauth/token')) { // 如果token快过期了
if (!isRefreshing) { // 控制重复获取token
isRefreshing = true
let formData = new FormData()
formData.append('grant_type', 'refresh_token')
formData.append('refresh_token', store.state.user.refresh_token)
axios({
url: '/identity/oauth/token',
method: 'post',
data: formData,
baseURL: cloudConfig.api_path,
timeout: cloudConfig.api_timeout,
headers: {
'Authorization': 'Basic ' + cloudConfig.Authorization
}
}).then(res => {
isRefreshing = false
// console.log(res)
if (res.status === 200) {
store.dispatch('loginIn', res.data)
onAccessTokenFetched(res.data.access_token)
}
}).catch(() => {
router.push({ path: '/login' })// 失败就跳转登陆
isRefreshing = false
})
}
// 将其他接口缓存起来
const retryRequest = new Promise((resolve) => {
// 返回Promise并且让其状态一直为等待状态,
// 只有当token刷新成功后, 就会调用通过addSubscriber函数添加的缓存接口,
// 此时, Promise的状态就会变成resolve
addSubscriber((newToken) => {
// 表示用新的token去替换掉原来的token
response.config.headers.Authorization = 'Bearer ' + newToken
// 替换掉url -- 因为baseURL会扩展请求url
response.config.url = response.config.url.replace(response.config.baseURL, '')
// 用重新封装的config去请求, 就会将重新请求后的返回
resolve(service(response.config))
})
})
return retryRequest
}
-------忽略以下代码
)
文章还有需改进的地方,只提供大概思路