脑壳痛,最近莫名其妙的安全抓得严,有安全团队就提了个要求,生产环境下的 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 语句,但代码在浏览器运行没报错,这里就没管他了。
就像这样:

優(yōu)網(wǎng)科技秉承"專業(yè)團隊、品質(zhì)服務(wù)" 的經(jīng)營理念,誠信務(wù)實的服務(wù)了近萬家客戶,成為眾多世界500強、集團和上市公司的長期合作伙伴!
優(yōu)網(wǎng)科技成立于2001年,擅長網(wǎng)站建設(shè)、網(wǎng)站與各類業(yè)務(wù)系統(tǒng)深度整合,致力于提供完善的企業(yè)互聯(lián)網(wǎng)解決方案。優(yōu)網(wǎng)科技提供PC端網(wǎng)站建設(shè)(品牌展示型、官方門戶型、營銷商務(wù)型、電子商務(wù)型、信息門戶型、微信小程序定制開發(fā)、移動端應(yīng)用(手機站、APP開發(fā))、微信定制開發(fā)(微信官網(wǎng)、微信商城、企業(yè)微信)等一系列互聯(lián)網(wǎng)應(yīng)用服務(wù)。