如何保护HTML DOM属性不被开发者工具(DEV tools)篡改?
嘿,太懂你这种纠结了——想把DBKEY这种和业务强关联的标识跟DOM元素绑在一起,方便后续操作,但data-*属性随便开个开发者工具就能改,用Object.freeze()又管不住DOM元素的属性,还不想碰虚拟DOM,确实得找个靠谱的替代方案。
给你几个实用的思路,都是不用虚拟DOM就能搞定的:
用内存映射表存关联关系,彻底和DOM解绑
别把敏感数据存在DOM上,而是在JS内存里维护一个映射表,把DOM元素和对应的DBKEY绑定起来。推荐用WeakMap,因为它会自动回收已销毁DOM元素的条目,不会造成内存泄漏。举个简单的例子:// 初始化一个WeakMap来存DOM元素和DBKEY的映射 const elementDbMap = new WeakMap(); // 给目标表格行绑定DBKEY const targetRow = document.querySelector('#user-row-123'); elementDbMap.set(targetRow, 'db-key-xxxx-1234'); // 后续需要用到DBKEY时,直接从映射表取 function handleRowClick(event) { const clickedRow = event.target.closest('tr'); const dbKey = elementDbMap.get(clickedRow); if (dbKey) { // 这里做你的业务逻辑,比如请求后端接口 } else { // 说明映射关系不存在,可能是DOM被篡改了 console.warn('无效的操作,数据可能已被篡改'); } }这种方式下,DOM上根本不会出现敏感数据,用户改DOM也碰不到内存里的映射关系,安全性拉满。
如果一定要存在DOM上,就加密后再存
要是你非得把数据存在data-*属性里,那至少要加密后再存储,使用时解密,还要加个校验逻辑防止篡改。比如用浏览器原生的Crypto API做对称加密(别用示例里的简单逻辑,生产环境要正经实现加密):// 示例加密逻辑(生产环境请用标准的AES加密方案) async function encrypt(data, key) { const encoder = new TextEncoder(); const dataBuffer = encoder.encode(data); // 生成随机向量 const iv = crypto.getRandomValues(new Uint8Array(12)); const encryptedBuffer = await crypto.subtle.encrypt( { name: 'AES-GCM', iv }, key, dataBuffer ); // 把向量和加密数据一起返回,解密时要用 return btoa(JSON.stringify({ iv: Array.from(iv), data: Array.from(new Uint8Array(encryptedBuffer)) })); } // 解密并校验 async function decrypt(encryptedStr, key) { try { const { iv, data } = JSON.parse(atob(encryptedStr)); const decryptedBuffer = await crypto.subtle.decrypt( { name: 'AES-GCM', iv: new Uint8Array(iv) }, key, new Uint8Array(data) ); const decoder = new TextDecoder(); return decoder.decode(decryptedBuffer); } catch (e) { // 解密失败,说明数据被篡改或格式错误 return null; } } // 生成加密密钥(建议从后端动态获取短期密钥,别硬编码在前端) const encryptionKey = await crypto.subtle.generateKey( { name: 'AES-GCM', length: 256 }, true, ['encrypt', 'decrypt'] ); // 存储加密后的DBKEY到data属性 const targetRow = document.querySelector('#user-row-123'); targetRow.dataset.encryptedDbkey = await encrypt('db-key-xxxx-1234', encryptionKey); // 点击时解密使用 targetRow.addEventListener('click', async () => { const encryptedKey = targetRow.dataset.encryptedDbkey; const dbKey = await decrypt(encryptedKey, encryptionKey); if (!dbKey) { console.error('数据已被篡改,拒绝操作'); return; } // 执行正常业务逻辑 });不过要说明,这种方式只能防普通用户,要是碰到懂行的扒了你的密钥还是能破解,所以这只能算前端层面的补充防护,核心还是得靠后端。
后端兜底校验才是最后一道防线
不管你前端怎么存数据,永远别信前端传过来的任何参数!比如用户点击表格行后,前端把DBKEY传给后端,后端必须做两件事:一是校验这个DBKEY对应的记录是否真实存在,二是校验当前登录用户是否有权限操作这条记录。哪怕前端把DBKEY改成别的用户的,后端校验不通过就直接拒绝请求,这才是最靠谱的安全措施。
对了,你提到的Object.freeze()确实没用,因为DOM元素是浏览器的宿主对象,freeze()只能冻结JS原生对象的属性,管不了DOM的属性,开发者工具照样能改,所以别在这上面浪费精力了。
备注:内容来源于stack exchange,提问作者McMurphy

