何为双 token
- accessToken : 用户获取数据权限
- refreshToken : 用来获取新的accessToken
双 token 验证机制,其中 accessToken 过期时间较短,refreshToken 过期时间较长。当 accessToken 过期后,使用 refreshToken 去请求新的 token。
无感刷新:
当客户端检测到access token即将过期或已经过期时,自动在后台向认证服务器发起请求,携带refresh token换取新的access token。这个过程对用户来说是无感知的,即用户不需要重新登录,页面也不会中断或刷新,因此被称为"无感刷新"。
实现方式:
当 accessToken 过期时,调接口,会返回201,表示token过期,这个时候需要调用 refreshToken 接口,重新获取新的token,在这个期间执行的请求,都存放到一个新的请求队列中,当获取新的token之后再重新调用,接口返回202的时候表示 refreshToken 过期,需要给用户提示,重新登录页面,跳转到登录页
具体步骤:
1.先再store 中定义接口,存储 refreshToken 和 accessToken ,定义刷新token接口,修改 refreshToken 和 accessToken
js
import { login,refreshToken} from '@/api/login'
const user = {
state: {
access_token: getStore({
name: 'access_token'
}) || '',
refresh_token: getStore({
name: 'refresh_token'
}) || '',
},
mutations: {
SET_ACCESS_TOKEN(state, token) {
state.access_token = token
setStore({
name: 'access_token',
content: state.access_token,
type: 'session'
})
},
SET_REFRESH_TOKEN: (state, rfToken) => {
state.refresh_token = rfToken
setStore({
name: 'refresh_token',
content: state.refresh_token,
type: 'session'
})
},
},
actions: {
// 登录
Login({ commit }, userInfo) {
const loginname = userInfo.loginname.trim()
const password = userInfo.password
return new Promise((resolve, reject) => {
login(loginname, password).then(res => {
clearStore()
clearStore({ type: 1 })
commit('SET_ACCESS_TOKEN', res.data.accessToken)
commit('SET_REFRESH_TOKEN', res.data.refreshToken)
resolve()
}).catch(error => {
reject(error)
})
})
},
// 刷新token
RefreshToken({ commit, state }) {
return new Promise((resolve, reject) => {
refreshToken({refreshToken:state.refresh_token}).then(res => {
if(res.success){
commit('SET_ACCESS_TOKEN', res.data.accessToken)
commit('SET_REFRESH_TOKEN', res.data.refreshToken)
}
resolve(res)
}).catch(error => {
reject(error)
})
})
},
}
}
export default user
2.再在请求拦截器中针对不同返回值来处理token情况
js
//1引入
import axios from 'axios'
import NProgress from 'nprogress' // progress bar
import { Message, MessageBox, Notification } from 'element-ui'
import store from '@/store'
import errorCode from '@/util/errorCode'
import 'nprogress/nprogress.css'
import { baseURL, buildEnv, env } from './baseURL'
axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
//2创建axios实例
let service = axios.create({
baseURL: baseURL[buildEnv],
timeout: 6000
})
// 返回其他状态吗
service.defaults.validateStatus = function (status) {
return status >= 200 && status <= 500 // 默认的
}
// 跨域请求,允许保存cookie
service.defaults.withCredentials = true
// NProgress Configuration
NProgress.configure({
showSpinner: false
})
//创建拦截器
service.interceptors.request.use(
config => {
NProgress.start()
// 是否需要设置 token
const isToken = (config.headers || {}).isToken === false
const token = store.getters.access_token
if (token && !isToken) {
config.headers['Authorization'] = token// 让每个请求携带自定义token
}
return config
},
error => {
console.log(error);
return Promise.reject(error);
}
)
//标志当前是否正在刷新token
var isRefreshing = true
//请求队列
var requests = []
service.interceptors.response.use(
response => {
NProgress.done()
let res = response.data;
const config = response.config;
if (!res.success) {
// 未设置状态码则默认成功状态
const code = Number(res.errorCode)
// 获取错误信息
const msg = res.errorMsg || errorCode[code] || errorCode.default['default']
// 令牌无效
if (code === 200 || code === 202) {
logout()
} else if (code === 201) {
//访问令牌过期
if (isRefreshing) {
isRefreshing = false
store.dispatch('RefreshToken')
.then(res => {
if (res.success) {
// 执行失效函数
requests.forEach((cb) => cb())
//重新请求完清空
requests = []
return service(config)
}
})
.finally(() => {
isRefreshing = true;
});
}
// 返回未执行 resolve 的 Promise
return new Promise(resolve => {
// 用函数形式将 resolve 存入,等待刷新后再执行
requests.push(() => {
resolve(service(config));
});
});
}
else {
Message({
message: msg,
type: 'error'
})
return Promise.resolve(res)
}
}
return Promise.resolve(res)
},
error => {
NProgress.done()
let { message } = error;
Message({
message: message,
type: 'error',
duration: 5 * 1000
})
return Promise.reject(error)
}
)
// 退出
const logout = () => {
MessageBox({
message: '登录状态已过期,请重新登录',
type: 'error',
lockClickModal: false // 设置为false,点击弹窗外围不关闭弹窗
}).then(() => {
store.dispatch('LogOut').then(() => {
store.commit('cleanMenu')
// 刷新登录页面,避免多次弹框
window.location.reload()
})
})
}
export default service