Vue 前端项目如何统一封装 Axios 拦截器处理 API 鉴权 Token?

文章导读
在 Vue 项目中统一处理 Token,最稳妥的做法是在 Axios 实例创建后,通过请求拦截器自动注入 Token,并在响应拦截器中处理过期刷新或跳转登录,适用于绝大多数前后端分离场景。
📋 目录
  1. 完整封装代码示例
  2. 核心逻辑解析
  3. 怎么验证是否生效
  4. 常见风险与排查
  5. 参考来源
A A

在 Vue 项目中统一处理 Token,最稳妥的做法是在 Axios 实例创建后,通过请求拦截器自动注入 Token,并在响应拦截器中处理过期刷新或跳转登录,适用于绝大多数前后端分离场景。

先说结论:封装拦截器是标准做法,但要注意 Token 存储安全和刷新逻辑的死循环问题

  • 适合:前后端分离、使用 Bearer Token 鉴权的项目
  • 先看:Token 存在 localStorage 还是 Cookie,决定读取方式
  • 建议:响应拦截器中处理 401 状态码时,增加防止无限刷新请求的标志
  • 安全:高敏感场景建议使用 HttpOnly Cookie 替代 localStorage 存储 Token

完整封装代码示例

以下代码包含请求拦截、响应拦截、Token 无感刷新及并发锁机制,可直接参考修改 src/utils/request.js

import axios from 'axios'
import { ElMessage } from 'element-plus' // 根据 UI 库调整
import router from '@/router'

const service = axios.create({
  baseURL: process.env.VUE_APP_BASE_API || '/api',
  timeout: 5000
})

let isRefreshing = false // 刷新锁
let requestQueue = [] // 请求队列

// 执行队列中的请求
const executeQueue = (token) => {
  requestQueue.forEach(cb => cb(token))
  requestQueue = []
}

// 请求拦截器
service.interceptors.request.use(config => {
  // 优先从 Cookie 读取,其次 localStorage,根据实际存储方式调整
  const token = document.cookie.replace(/(?:(?:^|.*;)\s*token\s*=\s*([^;]*).*)/, "$1") || localStorage.getItem('token')
  if (token) {
    config.headers['Authorization'] = `Bearer ${token}`
  }
  return config
}, error => {
  return Promise.reject(error)
})

// 响应拦截器
service.interceptors.response.use(response => {
  const res = response.data
  // 根据后端约定判断业务错误,例如 code !== 200
  if (res.code !== 200) {
    ElMessage.error(res.message || 'Error')
    return Promise.reject(new Error(res.message || 'Error'))
  }
  return res
}, error => {
  if (error.response && error.response.status === 401) {
    if (!isRefreshing) {
      isRefreshing = true
      // 调用刷新 Token 接口
      return refreshToken().then(newToken => {
        // 刷新成功,更新存储
        localStorage.setItem('token', newToken)
        executeQueue(newToken)
        // 重试当前请求
        error.config.headers['Authorization'] = `Bearer ${newToken}`
        return service(error.config)
      }).catch(() => {
        // 刷新失败,跳转登录
        localStorage.removeItem('token')
        router.push('/login')
        return Promise.reject(error)
      }).finally(() => {
        isRefreshing = false
      })
    } else {
      // 正在刷新,将当前请求加入队列
      return new Promise((resolve) => {
        requestQueue.push((token) => {
          error.config.headers['Authorization'] = `Bearer ${token}`
          resolve(service(error.config))
        })
      })
    }
  }
  return Promise.reject(error)
})

// 模拟刷新 Token 函数,实际需替换为真实 API
function refreshToken() {
  return new Promise((resolve, reject) => {
    axios.post('/auth/refresh').then(res => {
      resolve(res.data.token)
    }).catch(() => {
      reject()
    })
  })
}

export default service

核心逻辑解析

1. 请求拦截:统一读取存储中的 Token 并写入 Header,避免每个请求重复编写。

2. 响应拦截:捕获 401 状态码,区分是否正在刷新 Token。

3. 并发锁机制:使用 isRefreshingrequestQueue 暂存并发请求,待刷新完成后统一重试。

4. 异常处理:刷新失败或业务错误时,统一清理本地存储并跳转登录页,防止死循环。

怎么验证是否生效

1. 检查请求头:打开浏览器开发者工具 Network 面板,发起业务请求,确认 Request Headers 中是否自动携带 Authorization 字段。

Vue 前端项目如何统一封装 Axios 拦截器处理 API 鉴权 Token?

2. 模拟过期:手动将 localStorage 中的 Token 修改为过期值,发起请求,观察是否自动触发刷新接口且业务请求最终成功(状态码 200)。

3. 并发测试:同时发起多个请求,确保刷新 Token 接口只被调用一次,其他请求在刷新后自动重试。

常见风险与排查

1. 存储安全:localStorage 易受 XSS 攻击,若项目安全性要求高,建议后端配置 HttpOnly Cookie 存储 Token,前端无需手动读取,axios 会自动携带。

2. 刷新死循环:确保刷新 Token 的接口本身不会触发 401 拦截逻辑,可在刷新请求配置中增加 skipAuth: true 并在拦截器中判断跳过。

3. 并发问题:多个请求同时 401 时,必须加锁控制,否则会导致多次刷新请求,消耗服务器资源且可能导致 Token 失效。

4. 单元测试:在单元测试环境中,记得重置拦截器或 Mock axios 实例,避免污染其他测试用例。

参考来源

1. Axios 官方文档 - Interceptors 章节,https://github.com/axios/axios#interceptors