有一天,我们上线测试环境的 H5 版本,刚搭好不到 2 小时,就有人在群里发出完整页面截图、按钮事件名,甚至调试台打印的 Socket 参数。
你会说:“他们是不是拿到源码了?”
不,他们只是按了个 F12,打开了 Chrome DevTools,就把我们整个页面的结构看了个一清二楚。
这其实不是谁的错。Web 前端天然就是“公开”的,任何浏览器都能看到它。但我们能做的,是增加一点点难度,让调试、扒站、逆向变得麻烦,哪怕只是延缓。
这是很多人最先想到的问题——怎么防止用户打开 F12?
答案是:防不住,但可以恶心人。
我们曾经加过一个极简单的阻止调试的 JS:
document.onkeydown = function (e) {
if (e.key === 'F12' || (e.ctrlKey && e.shiftKey && e.key === 'I')) {
return false;
}
};
再进阶一点,可以用检测窗口尺寸的方式:
setInterval(function () {
if (window.outerHeight - window.innerHeight > 100) {
document.body.innerHTML = '请勿调试页面
';
}
}, 1000);
真正有经验的人会关掉 JS 执行,或者直接用抓包工具,所以我们后来就不靠这招了,而是从结构设计上做隐藏 + 加密 + 延迟加载。
很多项目偷懒,把核心逻辑、通信地址、用户 token 甚至 socket 实例都写在 index.html
里,打开浏览器的 Sources 面板就能看到全部。
这等于“自爆”。
我们后来一律把 index 页面写成这样:
然后把真正的逻辑都封在加密过的 JS 文件里,配合 JS 混淆器压缩到几乎不可读。
一个典型的混淆前后对比如下:
function connectServer(url) {
socket = io.connect(url);
socket.on('connect', () => {
console.log('连接成功');
});
}
var _0x24a2=["\x63\x6F\x6E\x6E\x65\x63\x74","\x63\x6F\x6E\x6E\x65\x63\x74\x65\x64"];
function _0x57af(_0x1e9d){
var _0x3e23=io[_0x24a2[0]](_0x1e9d);_0x3e23[_0x24a2[1]](function(){
console["log"]("连接成功")})
}
重点:不是让别人看不懂,而是让别人懒得看下去。
在实际部署时,我们常常把 HTML 主体隐藏起来,只在 JS 成功加载后再显示页面。这样做的好处是:
防止用户直接查看静态结构;
核心按钮、参数等都从 JS 控制生成;
结合本地缓存 + 加载动画提升体验。
实现方式很简单:
我们也可以加一段初始化逻辑判断 cookie 或缓存,避免盗链或直接访问。
这是前后端通信的“老套路”,尤其是在服务端 Socket 无法识别用户的情况下,签名机制可以防止伪造请求。
比如发出一个请求时,把时间戳、用户 ID、固定密钥生成一个签名:
function getSign(uid, timestamp) {
return md5(uid + 'SALT123' + timestamp);
}
然后请求附带:
{
uid: 1001,
ts: 1710000000,
sign: 'a3fbb1ce...'
}
服务端校验:
if (sign !== md5(uid + SALT + ts)) {
reject('签名错误');
}
是不是绝对安全?当然不是。但这招可以防止最基本的抓包伪造。
如果你的组件有多个页面,比如首页、设置页、房间页,建议不要做成静态多页,而是用动态渲染 + 路由加载。
我们实际项目中采用的方式是:
所有页面都引用一个基础模板;
JS 控制加载结构;
页面内容通过模板字符串插入 DOM;
页面跳转不刷新,而是用 hash 监听切换。
为什么这么做?很简单:页面结构不落地,就不容易被直接扒。
再配合 HTML 压缩混淆工具(比如 html-minifier),基本上 Sources 面板里能看到的就是一坨不读也罢的代码。
实际用过的几款推荐如下:
javascript-obfuscator
Node 版本混淆神器,配合 Webpack 插件使用效果更佳。
UglifyJS
老牌压缩工具,适合 ES5 项目,混淆能力一般。
obfuscator.io
在线混淆工具,适合快速测试。
我们一般用 javascript-obfuscator
配合打包:
javascript-obfuscator ./src --output ./dist
设置参数推荐:
{
"compact": true,
"controlFlowFlattening": true,
"deadCodeInjection": true,
"stringArray": true
}
写到这里,我必须说一句实话:
前端永远无法做到“安全”。
只要代码在浏览器里跑,就能被看见。我们做的这些混淆、隐藏、延迟加载,本质上不是“加密”,而是“拖延”。
——拖延别人逆向的时间,为你的服务端争取防护时间;
——拖延非专业者的破解兴趣,为项目测试提供正常窗口。
如果你做的是多人互动的项目,或者在维护一套源代码,前端的保护就是你最基本的一层“脸皮”——扒得动不要紧,但别扒得太容易。
下一章,我们将对几个典型组件进行功能设计解读:从“转盘抽奖”、“连线触发”到“古风角色玩法”的组件实现逻辑、资源调用结构、状态管理系统,一步步拆解背后的技术实现。