这是一个非常典型的“前端代码保护”需求。为了防止别人直接下载完整的 JS 文件,通常采用 “混淆 + 切片 + 动态加载 + 运行时重组” 的组合策略。
虽然前端代码在浏览器运行最终都会还原,但这种方法可以极大增加逆向工程的成本。
下面我将提供一个完整的 Node.js 解决方案,包含以下步骤:
原始代码准备。
构建脚本 (build.js):负责混淆代码、将代码切割成多个乱码片段、生成加载器。
运行环境 (index.html):通过加载器还原并运行代码。
准备工作
首先,你需要初始化项目并安装一个核心混淆库 javascript-obfuscator。
mkdir js-protector cd js-protector npm init -y npm install javascript-obfuscator --save-dev
1. 编写原始代码 (source.js)
这是你想要保护的完整业务逻辑代码。
// source.js
console.log("核心程序开始加载...");
function secretAlgorithm(a, b) {
return a * b + 100;
}
const userData = {
name: "Admin",
role: "SuperUser"
};
setTimeout(() => {
const result = secretAlgorithm(10, 20);
console.log(`计算结果: ${result}`);
console.log(`当前用户: ${userData.name}`);
alert("完整代码已成功动态加载并执行!");
}, 1000); 2. 编写构建/切割脚本 (build.js)
这个 Node.js 脚本是核心。它会做以下几件事:
读取 source.js。
使用 javascript-obfuscator 进行深度混淆。
将混淆后的字符串转换成 Base64 或 Hex(增加阅读难度)。
将字符串物理切割成多个 .dat 或 .txt 文件。
生成一个用于拼接和执行的 loader.js。
// build.js
const fs = require('fs');
const path = require('path');
const JavaScriptObfuscator = require('javascript-obfuscator');
// 配置
const CONFIG = {
inputFile: './source.js',
outputDir: './dist',
chunkCount: 4, // 将代码切成几份
chunkPrefix: 'data_', // 切片文件名前缀
};
// 1. 读取源码
const sourceCode = fs.readFileSync(CONFIG.inputFile, 'utf8');
// 2. 深度混淆源码
console.log("正在混淆代码...");
const obfuscationResult = JavaScriptObfuscator.obfuscate(sourceCode, {
compact: true,
controlFlowFlattening: true, // 控制流扁平化(让逻辑变乱)
deadCodeInjection: true, // 注入死代码
stringArray: true,
stringArrayEncoding: ['rc4'], // 字符串加密
splitStrings: true,
selfDefending: true, // 自我保护(防止格式化)
});
const obfuscatedCode = obfuscationResult.getObfuscatedCode();
// 3. 将混淆后的代码转换为 Base64 (防止传输过程中出现编码问题,同时增加一层肉眼不可读)
// 这里为了演示,我们在Base64基础上再做一个简单的异或操作或翻转,让它彻底不像代码
const encodedCode = Buffer.from(obfuscatedCode).toString('base64').split('').reverse().join('');
// 4. 切割字符串
console.log("正在切割代码...");
const totalLen = encodedCode.length;
const chunkSize = Math.ceil(totalLen / CONFIG.chunkCount);
const chunks = [];
if (!fs.existsSync(CONFIG.outputDir)) {
fs.mkdirSync(CONFIG.outputDir);
}
for (let i = 0; i < CONFIG.chunkCount; i++) {
const start = i * chunkSize;
const end = start + chunkSize;
const chunkContent = encodedCode.substring(start, end);
const fileName = `${CONFIG.chunkPrefix}${i}.bin`; // 使用 .bin 伪装成二进制数据
fs.writeFileSync(path.join(CONFIG.outputDir, fileName), chunkContent);
chunks.push(fileName);
console.log(`生成切片: ${fileName}`);
}
// 5. 生成加载器 (Loader)
// 加载器逻辑:下载所有切片 -> 排序 -> 拼接 -> 解码(反转+Base64解密) -> 执行(eval)
// 为了安全,加载器本身也应该被混淆
const loaderScript = `
(async function() {
const files = ${JSON.stringify(chunks)};
try {
// 并发下载所有切片
const promises = files.map(file => fetch(file).then(res => res.text()));
const contents = await Promise.all(promises);
// 拼接(因为我们在生成时是按顺序push的,Promise.all返回顺序也是一致的)
const fullString = contents.join('');
// 解码逻辑 (对应构建时的编码:先反转,再Base64解密)
const reversed = fullString.split('').reverse().join('');
const realCode = atob(reversed);
// 动态执行 (这是核心,利用 new Function 或 eval 在全局作用域执行)
// 使用 new Function 比 eval 稍微安全一点点,且不会影响当前闭包变量
const run = new Function(realCode);
run();
} catch (e) {
console.error("Loading failed");
}
})();
`;
// 混淆加载器本身
const obfuscatedLoader = JavaScriptObfuscator.obfuscate(loaderScript, {
compact: true,
stringArray: true,
unicodeEscapeSequence: true // 将字符串转为Unicode,增加阅读难度
}).getObfuscatedCode();
fs.writeFileSync(path.join(CONFIG.outputDir, 'loader.js'), obfuscatedLoader);
console.log("构建完成!请运行 dist/index.html");
// 生成一个简单的 index.html 用于测试
const htmlContent = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Dynamic Split Loading</title>
</head>
<body>
<h1>JS动态分片加载测试</h1>
<p>请打开控制台(F12)查看运行结果,并在Network面板观察分片加载。</p>
<!-- 引入加载器 -->
<script src="loader.js"></script>
</body>
</html>
`;
fs.writeFileSync(path.join(CONFIG.outputDir, 'index.html'), htmlContent);3. 运行构建
在终端执行:
node build.js
执行后,你会看到 dist 文件夹下生成了以下文件:
data_0.bin, data_1.bin, data_2.bin, data_3.bin (你的代码碎片,里面是乱码)
loader.js (混淆后的加载逻辑)
index.html (入口文件)
4. 测试运行
由于使用了 fetch API,浏览器通常不允许直接打开本地文件 (file://) 加载 AJAX 请求。你需要启动一个简单的 HTTP 服务器。
# 如果安装了 python cd dist python -m http.server 8080 # 或者使用 npm 的 http-server npx http-server ./dist
访问 http://localhost:8080。
你会看到的现象:
Network 面板:浏览器请求了 loader.js,随后并发请求了 4 个 .bin 文件。
Console 面板:输出了源码中的日志,弹出了 alert 框。
安全性:
如果你打开任何一个 .bin 文件,看到的是反转过的 Base64 乱码。
如果你看 loader.js,是一堆混淆过的逻辑,很难看出它是怎么拼接文件的。
没有一个单独的请求包含完整的 source.js。
核心原理总结与进阶建议
1. 为什么这种方法有效?网络欺骗:网络请求看到的是一堆名为 .bin 或 .png(你可以伪装后缀)的文件,且内容不是合法的 JS 语法,过滤器和爬虫容易忽略。
内存重组:完整的代码只在浏览器内存中短暂存在(字符串拼接后传给 new Function),磁盘上没有完整文件。
双重混淆:业务代码混淆了一次,加载逻辑又混淆了一次,攻击者需要先逆向加载器,知道拼接规则和解密算法,才能拿到混淆后的业务代码。
2. 如何进一步加强?虽然上面的方法已经防住了大部分普通人,但高手可以通过 Hook new Function 或 eval 拦截最终执行的字符串。
进阶方案:
自定义加密 (XOR/AES):不要只用 Base64。在 build.js 里用一个密钥(Key)对内容进行 AES 加密。在 loader.js 里解密。虽然 Key 必须在前端(或通过接口获取),但这增加了静态分析的难度。
技巧:把 Key 拆散藏在 HTML 的 DOM 属性里,或者通过 URL 参数传递。
WebAssembly (Wasm):将解密逻辑甚至核心业务逻辑用 C/Rust 编写并编译成 Wasm。JS 只负责下载加密数据传给 Wasm,Wasm 内部解密并执行(如果是纯计算逻辑)或返回给 JS。Wasm 的逆向难度比 JS 高一个数量级。
顺序打乱:目前的切片是 0, 1, 2, 3。你可以随机生成切片文件名(如 a1z.dat, 9qq.dat),然后在 loader.js 中维护一个映射表来还原顺序。
检测调试:在 source.js 中加入检测 DevTools 是否打开的代码(如 debugger 陷阱或时间差检测),如果发现正在被调试,则故意加载错误的切片或崩溃。
警告
前端没有绝对的安全。只要代码在用户的浏览器中运行,CPU 就必须能读懂它,理论上用户就能读懂它。以上手段只能做到“防君子不防小人”以及增加破解的时间成本。重要业务逻辑(如支付验证、权限判断)必须放在后端处理。
网友回复
如何破解绕开seedance2.0真人照片生成视频 限制?
python有哪些算法可以将视频中的每个帧图片去除指定区域水印合成新的视频?
iphone的激光雷达数据能否实时传输到three三维空间中?
豆包sora等ai视频生成大模型生成的视频水印如何去除?
python如何实现在电脑上拨号打电话给手机?
具身机器人与人形机器人区别?
nodejs如何将一个完整的js代码文件切割成不同的部分混淆后动态加载进入html运行?
为啥windows.onerror捕获js错误是这样的{"message":"Script error.","source":"","lineno":0,"colno":0,"stack":null,
2026年ai将全面接管编程?
WebMCP是干啥的?


