当前位置: 首页 > news >正文

十一、vue3后台项目系列——封装请求,存储token,api统一化管理,封装token的处理工具

一、前言(建议搭配第十二章进行阅读)

        上一篇我们搭建了登录页面,其中涉及到登录按钮之后触发的登录事件。在通过表单校验之后,会调用登录接口,获取token。之后再将token进行存储,可实现在有效期无需重复登录的功能,也是对一些需要登录之后才能访问的页面作一个权限控制,总之这个token很关键,只有在登录成功后,才能获取到该值,获取之后,我们应该存放在用户模块的状态管理中,以及存到cookie中。

二、为什么要存到cookie中

1. 自动携带,无需手动处理请求头

        Cookie 最核心的优势是浏览器自动管理:一旦服务器通过 Set-Cookie 指令将 Token 写入 Cookie,后续所有向该服务器的请求(包括同域、符合路径 / 域名规则的请求),浏览器都会自动将 Cookie 附加到请求头的 Cookie 字段中,无需前端手动写代码(如 axios.interceptors 拦截器添加 Authorization: Bearer Token)。

对比 localStorage 的差异
如果 Token 存在 localStorage,前端必须手动在每个请求中添加请求头,例如:

// localStorage 需手动处理请求头
axios.interceptors.request.use(config => {const token = localStorage.getItem('token');if (token) config.headers.Authorization = `Bearer ${token}`;return config;
});

而 Cookie 无需这一步,减少前端代码冗余,也避免遗漏请求的风险。

2. 支持「HttpOnly」安全属性,防御 XSS 攻击

XSS(跨站脚本攻击)是前端 Token 存储的最大威胁之一:攻击者通过注入恶意脚本(如 <script>盗取localStorage.token</script>),可轻松获取 localStorage/sessionStorage 中存储的 Token。

而 Cookie 支持 HttpOnly 属性:一旦设置 HttpOnly=true,浏览器会禁止前端通过 JavaScript(如 document.cookie)读取或修改该 Cookie,只能由浏览器在请求时自动携带。这就从根本上阻断了 XSS 脚本盗取 Token 的可能,安全性远高于 localStorage。

示例:服务器设置 HttpOnly Cookie
后端返回响应时,通过 Set-Cookie 指令添加 Token 并配置安全属性:

// 响应头示例(后端返回)
Set-Cookie: token=admin-token; HttpOnly; Secure; SameSite=Strict; Path=/; Domain=your-domain.com
  • HttpOnly:禁止前端 JS 访问,防 XSS;
  • Secure:仅在 HTTPS 协议下传输,防中间人窃取;
  • SameSite=Strict:仅同域请求携带,防 CSRF(下文会讲);
  • Path=/:该域名下所有路径的请求都携带 Cookie。

3. 支持「SameSite」属性,防御 CSRF 攻击

CSRF(跨站请求伪造)是另一种常见攻击:攻击者诱导用户在已登录的状态下,访问恶意网站并发送伪造请求(如转账、修改密码),利用浏览器自动携带的 Cookie 冒充用户操作。

Cookie 的 SameSite 属性 专门用于防御 CSRF:

  • SameSite=Strict:仅当请求来自「当前域名」时,浏览器才携带 Cookie(完全禁止跨站携带);
  • SameSite=Lax:仅允许「GET 方法的跨站导航请求」携带(如从百度跳转你的网站),禁止 POST 等修改型请求携带;
  • SameSite=None:允许跨站携带(需配合 Secure 属性,仅 HTTPS 可用)。

通过设置 SameSite=Strict/Lax,可大幅降低 CSRF 攻击风险。而 localStorage 存储的 Token 虽不直接触发 CSRF(需手动加请求头),但一旦被 XSS 盗取,风险同样极高。

4. 可配置「过期时间」,自动失效

Cookie 支持 Max-Age 或 Expires 属性,可设置 Token 的过期时间:

  • Max-Age=86400:Cookie 从创建起存活 86400 秒(1 天),过期后自动删除;
  • Expires=Wed, 17 Sep 2025 08:00:00 GMT:指定具体过期时间。

这让 Token 的「自动失效」机制更易实现,无需前端手动管理过期逻辑(如 localStorage 需要前端判断 expireTime 是否过期)。

5. 跨域场景的可控性(配合 CORS)

在跨域请求中(如前端域名 a.com 调用后端 api.b.com),Cookie 可通过 CORS 配置实现「跨域携带」:

  1. 后端需在响应头中设置 Access-Control-Allow-Credentials: true,允许跨域请求携带 Cookie;
  2. 前端请求时需配置 withCredentials: true(如 axios 中 axios.defaults.withCredentials = true);
  3. 后端 Set-Cookie 需指定 Domain=api.b.com 并确保 SameSite=None + Secure

虽然配置稍复杂,但相比 localStorage 在跨域时的「手动传 Token」,Cookie 的自动携带仍更便捷,且安全性(HttpOnly)仍在。

什么时候不适合用 Cookie 存 Token?

Cookie 也有局限性,以下场景更适合 localStorage/sessionStorage

  1. 前端需要主动读取 Token:例如前端需根据 Token 解析用户角色(如 JWT Token 可前端解码),而 HttpOnly Cookie 禁止前端读取;
  2. 跨域场景复杂且无需高安全:例如多个子域名间共享 Token,且无需防御 XSS(如内部管理系统);
  3. Token 体积过大:Cookie 有大小限制(约 4KB),若 Token 是长字符串(如包含复杂权限的 JWT),可能超出限制。

总结:Cookie 存 Token 的核心价值

优势具体作用对比 localStorage
自动携带减少前端代码,避免遗漏请求需手动加请求头,易遗漏
HttpOnly 属性防 XSS 盗取,安全性高可被 JS 读取,易遭 XSS 攻击
SameSite 属性防 CSRF 攻击不直接防 CSRF,但 XSS 风险更高
可配置过期时间自动失效,无需前端管理需前端手动判断过期时间

当你的项目需要高安全性(防 XSS/CSRF)、低前端维护成本(自动携带)时,Cookie 是 Token 存储的最优选择(尤其是后端渲染、管理系统、金融类应用)。

三、封装请求方法

好处我觉得有以下:

1.统一化管理、全局配置:比如统一设置请求base Url,统一设置请求需要携带的请求头,改base url时如果有更改,这时候替换就会很方便。

2.减少代码冗余:如果没有对请求进行封装,可能会有重复性的代码,比如:共同的请求base url,在更换时,要改动很多地方,以及更多相同的地方。

3.对请求、响应进行拦截做二次处理:可以对请求和响应做二次处理,包括对错误的统一处理,可配合element使用错误提示组件,对不同的响应码做统一的判断处理等等。

参考代码src/utlis/request.js/:

import axios from 'axios';
import { ElMessage, ElMessageBox } from 'element-plus';
import { useUserStore } from '@/store';
import { getToken } from '@/utils/auth';// 创建 axios 实例
const service = axios.create({baseURL: import.meta.env.VITE_APP_BASE_API,timeout: 5000
});// 请求拦截器 (保持不变)
service.interceptors.request.use(config => {const userStore = useUserStore();if (userStore.token) {config.headers['X-Token'] = getToken();}return config;},error => {console.error('request error:', error);return Promise.reject(error);}
);// 响应拦截器
service.interceptors.response.use(response => {const res = response.data;// 当后端返回的自定义状态码表示成功时,返回响应数据if (res.code === 200) {return res;} // 当自定义状态码不为 200 时,统一处理为业务错误ElMessage({message: res.message || '错误',type: 'error',duration: 5 * 1000});// 特别处理需要重新登录的业务状态码if (res.code === 401 || res.code === 508 || res.code === 512) {ElMessageBox.confirm('登录状态已过期,您可以取消以停留在此页面,或者重新登录', '登录过期', {confirmButtonText: '重新登录',cancelButtonText: '取消',type: 'warning'}).then(() => {const userStore = useUserStore();userStore.resetToken().then(() => {location.reload();});});}// 返回一个被拒绝的 Promise,业务代码将进入 catch 块return Promise.reject(new Error(res.message || '错误'));},error => {// 处理 HTTP 状态码错误(例如,404, 500)// 通常,后端的“账号或密码错误”不会返回 HTTP 401,而是自定义的业务状态码,// 所以这个拦截器通常只处理网络或服务器错误。// 如果后端确实返回了 HTTP 401,我们在这里统一处理一次if (error.response.status === 401) {ElMessage({message: '用户未授权,请重新登录',type: 'error',duration: 5 * 1000});const userStore = useUserStore();userStore.resetToken().then(() => {location.reload();});} else {ElMessage({message: error.message || '网络异常',type: 'error',duration: 5 * 1000});}return Promise.reject(error);}
);export default service;

四、对token的处理

推荐使用js-cookie,js-cookie 是一个轻量级的 JavaScript 库,专门用于简化浏览器中 Cookie 的创建、读取、修改和删除操作。它封装了原生 Cookie 操作的复杂细节,提供了简洁的 API,让开发者能更高效地处理 Cookie。

安装:

npm install -D js-cookie

在utlis文件下创建auth.js文件:

import Cookies from 'js-cookie'const TokenKey = 'Admin-Token'export function getToken() {return Cookies.get(TokenKey)
}export function setToken(token) {return Cookies.set(TokenKey, token)
}export function removeToken() {return Cookies.remove(TokenKey)
}

主要调用这些方法,就能配合cookie进行操作了。

五、统一管理api

对登录这样的用户操作相关的请求,我们对请求进行模块化区分。方便管理,代码结构清晰,不必都写在页面中。这时候封装好的请求方法就有作用了。

src/api/user.js

import request from '@/utils/request'export function login(data) {return request({url: '/admin/user/login',method: 'post',data})
}export function getInfo(token) {return request({url: '/admin/user/info',method: 'get',params: { token }})
}export function logout() {return request({url: '/admin/user/logout',method: 'post'})
}

六、模拟后端接口响应(mock)

mock/user.js

// 模拟不同用户返回的token
const tokens = {admin: {token: 'admin-token'},editor: {token: 'editor-token'}
}
// 俩种权限的用户信息
const users = {'admin-token': {roles: ['admin'],introduction: 'I am a super administrator',avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',name: 'Super Admin'},'editor-token': {roles: ['editor'],introduction: 'I am an editor',avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',name: 'Normal Editor'}
}export default [{url: '/admin/user/login',type: 'post',response: config => {const { username } = config.bodyconst { password } = config.bodyif((username === 'admin' || username === 'editor') && password === 'CDC123456'){return {code: 200,message: '登录成功',data: {token: tokens[username].token }}} else {return {code: 500,message: '用户名或密码错误'}}}},// 获取用户信息{url: '/admin/user/info',type: 'get',response: config => {const { token } = config.queryconst info = users[token]// mock errorif (!info) {return {code: 401,message: '登录错误,无法获取用户详情。'}}return {code: 200,data: info}}},// 退出操作{url: '/admin/user/logout',type: 'post',response: _ => {return {code: 200,data: 'success'}}}
]

七、更新vite配置

如果设置了开发环境和生产环境的不同baseURL,则需要在vite中声明,不然会跟mock中定义的地址不一样,产生404报错,原因就是mock中的地址不会携带这些环境的baseURL,而设置了这些环境的baseURL,则会在请求时带上,到时候俩者不对应就会导致地址不一致,产生报错。

主要是这一行:

 urlPrefix: '/dev-api'

完整vite.config.js

import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import { viteMockServe } from "vite-plugin-mock";
import { createSvgIconsPlugin } from "vite-plugin-svg-icons";
import path from "path";export default defineConfig({// 配置路径别名resolve: {alias: {"@": path.resolve(__dirname, "src"),},},plugins: [vue(),// 配置mock服务viteMockServe({// 本地开发时启用mocklocalEnabled: true,// mock文件所在目录mockPath: "./mock/",urlPrefix: '/dev-api'}),createSvgIconsPlugin({// 指定SVG图标存放目录(使用@别名的话可以写成 '@/icons/svg',但这里需要绝对路径)iconDirs: [path.resolve(process.cwd(), "src/icons/svg")],// 指定symbolId格式(与SvgIcon组件中的iconName对应)symbolId: "icon-[dir]-[name]",// 配置SVGO优化svgoOptions: {multipass: true,plugins: [{name: "removeAttrs",params: {attrs: ["fill", "fill-rule"],},},],},}),],
});


文章转载自:

http://gpmEjezc.LLqch.cn
http://FrAGQXRB.LLqch.cn
http://QAIQUAn9.LLqch.cn
http://KMisADYI.LLqch.cn
http://guex2y5E.LLqch.cn
http://eOeRr5xM.LLqch.cn
http://bV3Wvc0W.LLqch.cn
http://raTToqwl.LLqch.cn
http://jfZL7gaC.LLqch.cn
http://YUC7eJDv.LLqch.cn
http://wYaUJZkL.LLqch.cn
http://4HdmJtua.LLqch.cn
http://t4a3YC48.LLqch.cn
http://8RjIex5D.LLqch.cn
http://ffwvHau2.LLqch.cn
http://imqzXn56.LLqch.cn
http://w4rsuT2i.LLqch.cn
http://x66LOPp2.LLqch.cn
http://TDf0zdLP.LLqch.cn
http://EJkNd9kh.LLqch.cn
http://8Ip0HbQU.LLqch.cn
http://ybpBueTU.LLqch.cn
http://uGsjQLNn.LLqch.cn
http://pfFEbWae.LLqch.cn
http://7zU0RlWn.LLqch.cn
http://XWUyMoKY.LLqch.cn
http://Q80sKt8D.LLqch.cn
http://javc5LSS.LLqch.cn
http://NeODqDxn.LLqch.cn
http://vcWXTWsF.LLqch.cn
http://www.dtcms.com/a/387391.html

相关文章:

  • 一个OC的十年老项目刚接手编译报错:No Accounts: Add a new account in Accounts settings.
  • 苹果个人开发者如何实现应用下载安装
  • 【CSS】文档流
  • App 自动化:从环境搭建到问题排查,全方位提升测试效率
  • 微信小程序转uni-app
  • 深入理解线性回归与 Softmax 回归:从理论到实践
  • SSM-----Spring
  • ubuntu 24.04.02安装android-studio
  • WebRTC 定时任务Process Module
  • 【服务器挂掉了】A40和A800:“性能不足”和“系统崩溃”
  • EJS(Embedded JavaScript)(一个基于JavaScript的模板引擎,用于在HTML中嵌入动态内容)
  • 前端路由模式:Vue Router的hash模式和history模式详解
  • 信创电脑采购指南:选型要点与避坑攻略
  • 前端高级开发工程师面试准备一
  • window下Qt设置生成exe应用程序的图标
  • Linux(三) | Vim 编辑器的模式化架构与核心操作机制研究
  • Kubernetes 安全与资源管理:Secrets、资源配额与访问控制实战
  • Java基础知识总结(超详细)持续更新中~
  • 原生js过滤出对象数组中重复id的元素,并将其放置于一个同一个数组中
  • 《Python 对象创建的秘密:从 __new__ 到单例模式的实战演绎》
  • k8s 与 docker 的相同点和区别是什么?
  • Linux《线程(下)》
  • 第二部分:VTK核心类详解(第20章 vtkCamera相机类)
  • 线性回归与 Softmax 回归:深度学习入门核心模型解析
  • K8s配置管理:ConfigMap与Secret核心区别
  • 【Qt开发】显示类控件(四)-> QCalendarWidget
  • 【K8S系列】Kubernetes 调度与资源管理深度剖析:Requests、Limits、QoS 与 OOM
  • 小程序地图以及讲解的使用
  • 单分类线性逻辑回归
  • 使用POSTMAN 创建泛微OA流程