业务场景:有几个外部系统的接口,需要先通过一个获取 token 的接口获取到 token 后续业务接口需要在请求头中的 Authorization 带上 token。刚开始我在登录后获取了一次 token 并存储在 store 中,业务接口使用过的时候直接获取就可以了。后来发现这个 token 经常莫名其妙的过期,刚开始想着通过同步获取 token 后再请求业务接口,但是这些业务接口分散在多个子页面中,就会造成多写很多业务代码。所决定在这些外部接口使用一个特定的 axios 实例并在 response 的拦截处统一处理 token 问题
axios 实例代码:
import axios from 'axios'
import store from '@/store'
import { getTokenInfo } from '@/api/prophetApiMarket'
import { Message } from 'element-ui'
import debounce from 'lodash/debounce'
// 多个错误信息合并
const showMessage = debounce(function (message) {
Message({
message: message,
type: 'error',
duration: 3 * 1000
}, 300)
// 最大重复次数
const MAXREPEAT = 3
// 记录当前接口重复请求次数,超过3次就不再请求了
let countState = {}
// 重新设置token
async function refreshToken(ajax, response) {
// 获取token,data就是token字符串
const { data } = await getTokenInfo()
store.commit("setProphetToken", data)
// 重新请求当前之前没有token 的接口
response.config.headers['Authorization'] = "Bearer " + data
ajax.defaults.headers.common['Authorization'] = "Bearer " + data
// 初始实例
const ajax = axios.create({
timeout: 10000,
baseURL: '/prop'
// 返回拦截
ajax.interceptors.response.use(async response => {
if (response.status === 200) {
const { data } = response
// token过期
if (data.code === 403 || (data.code === 406 && data.message === 'TOKEN已过期')) {
countState[response.config.url] = countState[response.config.url] ?? 0
// 最大重复次数
if (countState[response.config.url] < MAXREPEAT) {
// 计数
countState[response.config.url]++
// 获取token
await refreshToken(ajax, response)
// 重新请求
return ajax(response.config)
} else {
// 重置计数,并返回结果
countState[response.config.url] = 0
return data
return data
} else {
console.log('responseError: ' + response.data.message)
showMessage(response.data.message)
return Promise.resolve(response)
}, error => {
console.log('responseError: ' + error)
showMessage(error.message)
export default ajax
引入上面文件使用:
import ajax from './request.js'
import store from '@/store'
// getToken
export const getTokenInfo = () => {
return ajax.post('/aaaa', {
secretKey: store.state.envVar.key,
userId: store.state.envVar.id
// getDataA
export const getDataA= (params) => {
return ajax.post('/getDataA', params)
上面的 getDataA 是需要设置 Authorization 请求头的,当调用之后,response 拦截且匹配到 token 已过期就会先去获取 token 然后再重新请求
axios使用详见官网:axios中文文档|axios中文网
axios+ token +refreshToken
前端登录后,后端返回token和refresh_token,当token值过期的时候就用refresh_token去获得新的token
为了防止多次刷新token值,如果refreshToken的接口还没有返回,此时如果再有一个请求进来,会再次执行refreshToken ,这样就会导致多次执行刷新token的接口,因此需要防止这个问题,我们可以在request.js中使用一个参数例如flag来标记当前是否为正在刷新的状态,如果正在刷新则不在
vue-resource 拦截器使用
在vue项目使用vue-resource的过程中,临时增加了一个需求,需要在任何一个页面任何一次http请求,增加对token过期的判断,如果token已过期,需要跳转至登录页面。如果要在每个页面中的http请求操作中添加一次判断,那么会是一个非常大的修改工作量。
vue-resource的interceptors拦截器的作用正是解决此需求的妙方。在每次http的请求响应之后,如果设置了拦截器如下,会优先执行拦截器函数,获取响应体,然后才会决定是否把response返回
对 Token 进行刷新续期,我们要解决并发请求导致重复刷新 Token 的问题,这也是设计刷新 Token 的难点。这里我会分别介绍前端和后端各自的处理方案。
后端方案:利用 Redis 缓存
当同时发起多个请求时,第一个接口刷新了 Token,后面的请求仍然能通过请求,且不造成 Token 重复刷新。那么,后端在用户第一次登录时,需要将生成的 Token 数据(token 和 createTime)缓存一份到 Redis 中。
当 Token 过期时,重新生成新的 Token 数据并更新 Redis 缓
保持登录的实现有很多,都有个自的优缺点,作为前端当然最喜欢后台注入cookie,后台自己管理session时效。这当然只是期望,还是很多用token和refresnToken的机制。
而管理token和刷新token就是一个问题了,比如判断过期时间到了去刷新token之后再请求,同时并发的请求你不能每个都去刷新一次token,只刷新一个,那并发的请求还是老的token。
解决的方法有两种,一种是请求之前处理,把同时并发的请求都挂起,用promise。这个方法不好的就是要新增白名单,那些不需要刷新token的
post (target, params = {}) {
let suffix = Object.keys(params).map(name => {
return `${name}=${params[name]}`
}).join('&')
let urls = suffix....
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
return config;
error => {
return Promise.reject(error);
其中,`localStorage.getItem('token')`是获取存储在本地的token,`config.headers.Authorization`是设置请求头中的Authorization字段,Bearer是一种常用的身份验证方式。
has been blocked by CORS policy: Request header field secret is not allowed by Access-Control-Allow-
19148
webstorm报错cliEngineCtor is not a constructor this.options.parse is not a function at ESLintPlugin
CSDN-Ada助手:
每日一题(十)function showCase(value){ switch(value){ case 'A': console.log('case
m0_67135068:
echarts dataZoom 基本用法,给 yAxis 增加滚动条
软件园的肖邦:
error: src refspec master does not match any error:failed to push some refs to 'git@github.com:xxxx
pointer *:
webstorm报错cliEngineCtor is not a constructor this.options.parse is not a function at ESLintPlugin
cezxktguml:
webstorm报错cliEngineCtor is not a constructor this.options.parse is not a function at ESLintPlugin
前端项目中代码格式化配置:eslint + prettier + stylelint + pre-commit
vue3 element-plus el-dialog 二次封装,多层调用