广州总部电话:020-85564311
广州总部电话:020-85564311
20年
互联网应用服务商
请输入搜索关键词
知识库 知识库

优网知识库

探索行业前沿,共享知识宝库

安全:生产环境不要在 JS 中暴露后端接口地址,麻烦给写一个 vite 插件处理下

发布日期:2025-07-30 08:55:57 浏览次数: 812 来源:前端路引
推荐语
前端安全新思路:用Vite插件巧妙隐藏生产环境API地址,兼顾安全与开发效率。

核心内容:
1. 安全需求背景与前端API地址暴露问题分析
2. 基于异或加密的轻量级解决方案实现
3. Vite插件开发全流程与自动化替换技巧
小优 网站建设顾问
专业来源于二十年的积累,用心让我们做到更好!

脑壳痛,最近莫名其妙的安全抓得严,有安全团队就提了个要求,生产环境下的 JS 中不想看到后端接口地址。

说实话,就算 JS 文件中看不到接口地址,也没办法掩饰前端往后端发送网络请求的事实,只是做了一个掩耳盗铃的效果而已。

既然人家安全专家提出了要求,那就只有改吧改吧了~~

由于项目已经上线有一段时间了,有一大堆的后端 API 接口地址存在,必然不可能一个一个的去修改!!

就只能折腾一下 vite 插件,让 JS 插件在构建时候自动替换接口地址了。

准备加解密方法

第一步,肯定需要一对加解密方法,用来处理字符串的加解密,由于咱只需要掩盖接口地址,而且代码在浏览器端运行,安全性啥的没办法保证,就用一个最简单的异或加密就行了:

123456789101112131415161718192021222324252627282930313233// 简单的 XOR 加密函数function encrypt(str, key) {  let result = '';  for (let i = 0; i < str.length; i++) {    const charCode = str.charCodeAt(i) ^ key.charCodeAt(i % key.length);    // 转换为16进制字符串,确保可打印字符    result += ('00' + charCode.toString(16)).slice(-2);  }  return result;}// 解密函数function decrypt(encrypted, key) {try {    let result = '';    for (let i = 0; i < encrypted.length; i += 2) {      const hex = encrypted.substr(i, 2);      const charCode = parseInt(hex, 16) ^ key.charCodeAt((i/2) % key.length);      result += String.fromCharCode(charCode);    }    return result;  } catch(e) {    console.error('[API Decrypt Error]', e);    return'';  }}/* 测试代码 */var key = 'test-string-xxx'var code = encrypt('/api/login', key)console.log(code);var str = decrypt(code, key)console.log(str)

有了上面两个加解密方法,那就可以开始折腾 vite 插件了~~

vite 插件

这一步一言难尽,各种折腾,具体过长就不摆了,最后有了一个 vite-plugin-api-encrypt.js 插件文件,内容如下:

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576// 简单的 XOR 加密函数function encrypt (str, key) {  let result = '';  for (let i = 0; i < str.length; i++) {    const charCode = str.charCodeAt(i) ^ key.charCodeAt(i % key.length);    // 转换为16进制字符串,确保可打印字符    result += ('00' + charCode.toString(16)).slice(-2);  }  return result;}// 生成解密函数function generateDecryptFunction (key, functionName = 'decrypt') {  return `// API路径解密函数const ${functionName} = (encrypted) => {  try {    let result = '';    for (let i = 0; i < encrypted.length; i += 2) {      const hex = encrypted.substr(i, 2);      const charCode = parseInt(hex, 16) ^ '${key}'.charCodeAt((i/2) % ${key.length});      result += String.fromCharCode(charCode);    }    return result;  } catch(e) {    console.error('[API Decrypt Error]', e);    return '';  }};`;}export default function (options = {}) {  const {    key = 'api-encode-key',    decryptName = '__decryptApi' // 全局解密函数名  } = options;  return {    name: 'vite-plugin-api-encrypt',    transform (code, id) {      // 只处理JS/TS文件      if (!/\.(js|ts|vue)$/.test(id)) return;      // 匹配 /api/xxx 格式的字符串      const regex = /['"`](\/api\/[^'"`]+)['"`]/g;      let transformedCode = code;      let match;      while ((match = regex.exec(code)) !== null) {        const [fullMatch, apiPath] = match;        // 加密路径        const encrypted = encrypt(apiPath, key);        // 替换为解密函数调用        transformedCode = transformedCode.replace(          fullMatch,          `${decryptName}('${encrypted}')`        );      }      return transformedCode;    },    transformIndexHtml (html) {      // 注入全局解密函数到HTML头部      return html.replace(        '<head>',        `<head>          <script>            // 运行时解密函数            ${generateDecryptFunction(key, decryptName)}          </script>`      );    }  };}

然后在 vite.config.js 中配置使用插件:

123456789101112131415161718192021222324import { fileURLToPath, URL } from 'node:url'import { defineConfig } from 'vite'import vue from '@vitejs/plugin-vue'import vueDevTools from 'vite-plugin-vue-devtools'import apiEncrypt from './vite-plugin-api-encrypt'; // 插件路径// https://vite.dev/config/export default defineConfig({  plugins: [    vue(),    vueDevTools(),    apiEncrypt({      key: 'key', // 自定义加密 key      decryptName: '__decrypt' // 自定义全局函数名    })  ],  resolve: {    alias: {      '@': fileURLToPath(new URL('./src', import.meta.url))    },  },})

启动项目后,效果还不错,会将所有 /api 前缀的字符串都进行加密,再套一个解密方法,就像下面这样:

到这一步已经能解决接口地址暴露问题了,但还是不够完美,解密方法被全局暴露在 index.html 文件中,这也太明显了,有没有办法在需要解密的文件中注入一个解密方法,不要全局暴露??

完善插件

解决问题:在需要的位置插入解密方法,不要在 index.html 中全局暴露解密方法!!

修改 vite-plugin-api-encrypt.js 文件:

123456789101112131415161718192021222324252627282930313233343536373839404142// 其他部分不变,只需要修改暴露的插件对象export default function (options = {}) {  const {    key = 'api-encode-key',    decryptName = '__decryptApi' // 全局解密函数名  } = options;  return {    name: 'vite-plugin-api-encrypt',    transform (code, id) {      // 只处理JS/TS文件      if (!/\.(js|ts|vue)$/.test(id)) return;      // 匹配 /api/xxx 格式的字符串      const regex = /['"`](\/api\/[^'"`]+)['"`]/g;      let transformedCode = code;      let match;      let hasApiPath = false;      while ((match = regex.exec(code)) !== null) {        const [fullMatch, apiPath] = match;        // 加密路径        const encrypted = encrypt(apiPath, key);        // 替换为解密函数调用        transformedCode = transformedCode.replace(          fullMatch,          `${decryptName}('${encrypted}')`        );        hasApiPath = true;      }      // 如果文件中有API路径,注入解密函数      if (hasApiPath) {        const decryptCode = generateDecryptFunction(key, functionName);        transformedCode = `${decryptCode}\n${transformedCode}`;      }      return transformedCode;    },  };}

执行 build 命令构建之后,代码就是这样的:

每次插入的解密方法会造成代码重复,不过影响不大,解密方法就那么几行而已。如果实在有代码洁癖,还是可以考虑将解密方法抽离出来插入到 index.html 入口文件中!!


这里有一个小小的问题:解密方法插入的文字在文件开头,按照 ES 规范来说,文件开头应该是所有的 import 语句,但代码在浏览器运行没报错,这里就没管他了。

就像这样:


优网科技,优秀企业首选的互联网供应服务商

优网科技秉承"专业团队、品质服务" 的经营理念,诚信务实的服务了近万家客户,成为众多世界500强、集团和上市公司的长期合作伙伴!

优网科技成立于2001年,擅长网站建设、网站与各类业务系统深度整合,致力于提供完善的企业互联网解决方案。优网科技提供PC端网站建设(品牌展示型、官方门户型、营销商务型、电子商务型、信息门户型、微信小程序定制开发、移动端应用(手机站APP开发)、微信定制开发(微信官网、微信商城、企业微信)等一系列互联网应用服务。


我要投稿

姓名

文章链接

提交即表示你已阅读并同意《个人信息保护声明》

专属顾问 专属顾问
扫码咨询您的优网专属顾问!
专属顾问
马上咨询
扫一扫马上咨询
扫一扫马上咨询

扫一扫马上咨询