脱敏必须在前端提交前完成且不能依赖表单自动序列化,因浏览器提交时始终读取 input.value 原始值;CSS遮盖、属性伪装、onsubmit未阻止默认行为、仅改DOM不改FormData等均无效;正确做法是拦截submit,用FormData读取并替换敏感字段值后fetch发送。

跨表单提交敏感字段时,脱敏必须在数据离开前端前完成,且不能依赖表单自动序列化——否则明文会直接进 payload。
为什么原生 <form></form> 提交无法靠 CSS 或属性遮盖实现脱敏
很多人试图用 text-security: disc、data-masked="true" 或伪元素覆盖输入框内容,但这完全无效:表单提交时浏览器读取的是 input.value 原始值,跟页面上“看起来什么样”毫无关系。用户右键检查元素、控制台打 document.querySelector('[name="idcard"]').value,立刻拿到明文。
常见错误包括:
- 把脱敏逻辑写在
onsubmit里但没阻止默认行为,导致原生提交仍走明文路径 - 用 JS 改变
input.value后没同步更新 DOM 属性(如input.setAttribute('value', masked)),而某些老浏览器只读 attribute - 对多个同名
input(如动态添加的身份证号字段)只处理第一个,其余照常提交明文
FormData 构造时手动替换敏感字段值
这是目前最可控、兼容性最好的做法:拦截表单提交,用 new FormData(form) 读取原始数据,再对已知敏感字段做替换,最后用 fetch 发送。关键点是——别改 DOM,只改 FormData 实例内部的键值对。
立即学习“前端免费学习笔记(深入)”;
示例(手机号脱敏):
form.addEventListener('submit', e => {
e.preventDefault();
const fd = new FormData(form);
const phone = fd.get('phone');
if (phone && /^\d{11}$/.test(phone)) {
fd.set('phone', phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2'));
}
fetch('/api/submit', { method: 'POST', body: fd });
});
注意:
-
fd.get()和fd.set()是标准 API,IE 不支持,需 polyfill 或降级为URLSearchParams(仅限application/x-www-form-urlencoded场景) - 若后端接收数组(如多选证件类型),要用
fd.getAll('id_type')遍历处理,不能只get - 文件字段(
<input type="file">)不能被字符串替换,这类字段必须由后端脱敏或前端上传前预处理
隐藏域(<input type="hidden">)里的敏感值更危险
很多人把脱敏后的值塞进 hidden 字段再提交,以为“看不见就安全”。但只要字段 name 写对了,它就会和明文字段一起进 payload——而且更容易被漏掉校验。真正该放 hidden 的,是服务端已确认的上下文(如 session_id、order_token),不是脱敏结果。
正确姿势:
- 删掉所有用于“前端脱敏中转”的 hidden 字段,比如
<input name="masked_phone" type="hidden"> - 如果必须保留原始值供审计(如日志比对),应由后端生成脱敏值并返回给前端展示,前端只提交不可逆的脱敏串,不传原始值
- 检查所有
name属性是否与后端约定一致;name="userPhone"和name="user_phone"在后端可能是两个不同字段,其中一个可能根本没做脱敏逻辑
最易被忽略的一点:表单提交链路中,任何中间环节(比如埋点 SDK 自动采集 form.submit 事件、RUM 工具抓取 input 变化)都可能偷偷把明文塞进日志或监控系统。脱敏不是“让后端收不到明文”,而是“让明文从不出现在前端内存里”。



















