首页/新闻资讯/正文详情

前端数据脱敏实战:从工具函数到四道防线的完整方案

发布时间:2026/9/25 13:39:50 来源:云帆数科 栏目:资讯中心
前端数据脱敏实战:从工具函数到四道防线的完整方案
1. 一次事故复盘线上订单页泄露了客户完整手机号如果你所在团队的前端代码从来没有出过数据泄露事故那你大概率还没遇到过客户截图投诉这种场面。我印象最深的一次是某个后台管理系统的订单列表页直接在表格里渲染了客户完整手机号——一个负责运营的小姑娘对订单时顺手截图发到群里然后客户发现了自己的号码被完整暴露。起因很简单后端接口返回了明文mobile字段前端td{{ row.mobile }}/td直接渲染没有做任何处理。这类问题前端普遍存在原因是大家默认数据安全是后端的事。后端确实会做传输加密、权限校验、数据库加密存储但数据到达浏览器之后前端对数据的一切展示、缓存、上传行为都不再受服务端控制。手机号在接口响应里是一串明文就意味着任何能打开浏览器控制台的人都能直接看到只要页面渲染过完整号码用户在视觉上也可能通过截图、录屏二次传播。Web前端脱敏处理本质上就是在数据已经被合法获取的前提下对展示层、交互层、存储层做二次收敛让不该被看到的部分在源头就不出现。它的应用范围非常广管理后台的客户列表、客服工作台的订单详情、运营报表里的用户画像、甚至你自己写的个人中心页面——凡是涉及手机号、身份证号、银行卡号、邮箱、家庭住址、IP地址、车牌号、企业税号等信息的位置都应该有脱敏逻辑。这篇文章我会把它拆成六个部分来讲先界定前端脱敏的边界哪些该做、哪些不该做然后按数据到达浏览器的不同阶段给出完整方法矩阵接着讲最常用的正则脱敏与格式化脱敏怎么写再深入两个绝大多数团队会忽略的问题——数值精度泄露与日志/缓存泄露最后给一套可以直接落地的脱敏工具库设计与组件封装建议。你可以根据自己项目的紧急程度直接跳到对应章节抄作业。2. 前端脱敏的边界哪些该做、哪些不该做2.1 前端脱敏不是安全边界而是减少暴露面的工程手段我见过不少刚接触这个主题的同学把前端脱敏当成了数据安全方案觉得只要页面显示成138****5678就万事大吉。这是典型的认知偏差。前端脱敏做的只是让不该被看到的数据不出现在眼前它无法阻止以下几种情况用户直接调用后端接口获取完整数据黑客通过中间人攻击截获传输层数据已授权人员客服、运营通过合法接口导出原始数据所以先摆正定位前端脱敏是纵深防御中位于末端的一道展示层防线它的价值是降低意外泄露概率而不是替代后端权限控制。后端依然要保证数据库不存明文、接口按角色返回最小字段集、操作日志可追溯。前端脱敏配合后端接口字段裁剪比如后端直接把手机号处理成138****5678再下发才是完整方案。2.2 明确必须脱敏的数据类型基于个人信息保护相关法规的合规要求和一般行业实践建议把所有涉及个人身份识别信息PII的字段都纳入脱敏范围业务上常见的包括数据类型原始示例脱敏示例建议规则手机号13812345678138****5678保留前3后4中间4位打码身份证号110101199001011234110101********1234保留前6后4中间8位打码银行卡号6222021234567890123622202******9012保留前6后4中间打码邮箱zhangsangmail.comz***ngmail.com保留首字母和后的域名用户名部分打码家庭住址北京市朝阳区XX路XX号院3-202北京市朝阳区********保留到区县或街道层级车牌号京A12345京A***45保留省份尾号两位企业税号91110000MA01ABCDEF91110000********保留前8后4IP地址192.168.1.100192.168.*.*隐藏后两段提示脱敏规则没有绝对的标准答案同一类字段在不同业务场景下允许保留的位数都不一样。比如客服核对订单时需要看到手机号后四位与用户确认那后四位就必须完整保留但如果只是运营看统计数据可能连手机号都不该出现在页面上。规则由业务需求驱动而不是拍脑袋决定。2.3 前端脱敏的三不做除了明确做什么我更建议团队把不做什么也写进规范很多脱敏翻车事故恰恰是因为过度设计。一不做不脱敏用户自己主动输入的内容。用户在表单里输入的手机号、身份证号如果一边输入一边打码会导致用户完全不知道自己输的对不对。正确的做法是输入时不打码、失焦后或提交确认时做脱敏回显。二不做不脱敏用户自己发布、且期望公开的数据。比如用户在自己主页填写的公开联系方式那本来就是要给别人看的前端再打个码反而影响产品功能。三不做不把脱敏逻辑放在单页面内反复复制。脱敏代码到处散落不同页面规则不一致本身就制造了新的泄露风险——比如订单页打成138****5678详情页却写了个正则只隐藏中间三位一个订单号在列表和详情里出现两种形态用户一看就知道哪边处理不到位。这类问题在下面第4节会重点展开。3. 数据到达浏览器后的四道防线前端脱敏方法矩阵前端脱敏不是只有一个打个星号的动作。从后端下发数据到用户最终看到内容中间存在四个可以实施脱敏的阶段每道关卡的改动成本、覆盖范围和风险都不一样。标题里提到的方法矩阵说的就是这四个阶段的选择与搭配。3.1 第一道防线后端接口返回前预处理保证字段最小化严格来说这个阶段不在前端代码里但它决定了前端拿到的原始数据什么样是整个链路里最推荐做、也最容易被忽略的一步。推荐的做法是后端按角色返回视图模型比如订单列表接口对普通运营只返回mobileMasked字段对客服主管才额外返回mobile完整字段。前端在这个阶段能做的配合是在 TypeScript 类型定义里把字段名直接定义成mobileMasked不要定义成可选的mobile?。很多项目接口返回里同时包含mobile和mobileMask两个字段前端图省事直接用完整的那个等于后端辛辛苦苦做的裁剪被前端绕过了。类型层面把完整字段直接去掉是最有效的不让用。3.2 第二道防线数据请求层拦截处理统一入口改写如果后端接口暂时改不动或者是一个多团队协作的遗留系统前端可以在自己的请求层axios/fetch 封装做统一拦截。以 axios 响应拦截器为例import { maskField } from ./desensitize; const DESENSITIZE_MAP { mobile: mobile, phone: mobile, tel: mobile, idCard: idCard, idNo: idCard, bankCard: bankCard, email: email, address: address, }; axios.interceptors.response.use((response) { const data response.data; if (data typeof data object) { response.data maskDeep(data, DESENSITIZE_MAP); } return response; });这种方式的优点是一处生效全站覆盖新增接口不用单独处理缺点是处理的是响应体如果业务代码依赖原始字段做排序、搜索、复制脱敏后的数据就会干扰这些功能。所以响应拦截器方案更适合纯展示型后台页面不适合交互复杂的编辑型页面。3.3 第三道防线组件层展示处理渲染时按需打码组件层是前端脱敏的主战场也是绝大多数团队实际落地的地方。核心思路是数据流里保留完整字段但渲染时只展示脱敏后的内容。这样可以兼顾页面显示安全和业务逻辑可用两个目标——排序还是按完整手机号排搜索还是能搜到完整号码但用户看到的是打码后的版本。Vue 的过滤器、React 的包装组件、通用的工具函数都可以完成这一层。我会在下一节详细给出可复用的写法这里只强调一个容易犯错的点任何组件层脱敏都必须是展示专用的不能在业务数据对象上直接改值否则你改了row.mobile下次用这个对象做导出、生成 PDF、调用第三方接口时传递给下游的数据反而是残缺的。3.4 第四道防线浏览器渲染后的二次防护防截图、防扩散前三个阶段的脱敏解决的是正常操作下数据不外露。但脱敏后的页面仍可能被截图、录屏、拍照转发——比如屏幕上同时显示了客户的姓名和部分手机号这两者在同一画面中出现本身就是一种关联泄露。第四道防线是面向终端的敏感页面禁止系统原生截图移动端可以利用navigator.setAppBadge之外的系统能力或者在前端监测visibilitychange当应用切到后台时自动隐藏敏感内容并加一层系统级水印。页面水印在敏感页面上叠加半透明水印水印内容包含当前操作者工号、时间戳、页面路径。一旦截图外传可以追溯来源。水印实现建议用 canvas 绘制背景而不是叠加多层 DOM避免影响页面性能。剪贴板拦截拦截 copy 事件如果复制内容里包含手机号或身份证正则命中的完整数据强制改为脱敏内容或给出二次确认提醒。这套四道防线并不需要每次都全上而是根据页面敏感度做分级只含手机号的普通列表最少做到第三道含身份证、银行卡的高危页面必须做到第四道面向客服的查询页面甚至应该同时启用水印和剪贴板拦截。4. 脱敏工具库设计正则、格式化与组件化的完整实践4.1 一个支持链式调用的脱敏函数怎么写我在团队里维护了一个desensitize.js工具库核心是一个支持链式调用的mask函数。它接收一个字符串和一个规则描述对象输出脱敏后的字符串。这样写的好处是规则之间可以任意组合比如一个字段可能同时需要隐藏中间段 换成全角字符这样的复合规则。const mask (value, rules []) { if (value null || value undefined) return ; let result String(value); for (const rule of rules) { if (rule.type mobile) { result result.replace(/^(\d{3})\d{4}(\d{4})$/, $1****$2); } if (rule.type idCard) { result result.replace(/^(.{6}).{8}(.{4})$/, $1********$2); } if (rule.type email) { const [name, domain] result.split(); result name[0] *** domain; } if (rule.type all) { result result.replace(/[\s\S]/g, *); } } return result; }; // 使用示例 mask(13812345678, [{ type: mobile }]); // 138****5678 mask(zhangsangmail.com, [{ type: email }]); // z***gmail.com mask(123, [{ type: all }]); // ***实际生产里不建议每个规则都用if堆叠更推荐用规则表驱动const RULES { mobile: (v) v.replace(/^(\d{3})\d{4}(\d{4})$/, $1****$2), idCard: (v) v.replace(/^(.{6}).{8}(.{4})$/, $1********$2), email: (v) { const [name, domain] v.split(); return name[0] *** domain; }, bankCard: (v) v.replace(/^(\d{6})\d(\d{4})$/, $1********$2), address: (v) v.replace(/^(.{4}).$/, $1****), name: (v) v[0] *.repeat(Math.min(v.length - 1, 2)), };规则表的好处是方便单元测试覆盖也方便在项目里用 JSON 配置不同环境下的展示策略——开发环境可以显示完整数据便于调试测试与生产环境强制脱敏。4.2 深度遍历处理嵌套对象里的敏感字段接口返回的数据几乎不可能都是扁平结构订单详情里有用户对象、收货地址数组、操作日志列表。写一个通用的深拷贝遍历函数配合上面规则表可以递归处理任意层级的敏感字段。const SENSITIVE_KEYS [mobile, phone, idCard, bankCard, email, address]; const maskDeep (obj, keyMap SENSITIVE_KEYS) { if (Array.isArray(obj)) { return obj.map((item) maskDeep(item, keyMap)); } if (obj typeof obj object) { const result {}; for (const key of Object.keys(obj)) { const type keyMap.includes(key) ? keyMapKeyToRule(key) : null; result[key] type ? RULES[type](obj[key]) : maskDeep(obj[key], keyMap); } return result; } return obj; };使用的时候要特别注意一件事深拷贝遍历会改变数据源。如果你把它挂在 axios 拦截器上那么整个应用拿到的数据都是脱敏后的但如果你只是渲染前 copy 一份再脱敏那展示安全了但数据源里的完整字段仍然暴露在内存里。需要结合第3节的分级方案来选择到底在哪一层使用。4.3 在 Vue 和 React 中的组件化封装工具函数解决的是算法问题组件封装解决的是使用习惯问题。以 Vue 3 为例最推荐的封装方式是自定义指令v-mask它能让模板代码保持干净同时强制约束每个敏感字段必须显式声明脱敏规则。script setup // 指令定义 const vMask { mounted(el, binding) { const raw el.innerText.trim(); const rule binding.value || mobile; el.textContent mask(raw, [{ type: rule }]); }, update(el, binding) { const raw el.innerText.trim(); const rule binding.value || mobile; if (raw) el.textContent mask(raw, [{ type: rule }]); }, }; /script template span v-maskmobile{{ row.mobile }}/span /templateReact 侧的惯例是写一个MaskText组件接收value和rule两个 props内部调用mask函数后渲染脱敏文本。组件化最大的价值不是代码复用而是在代码评审时一眼就能看出哪些字段是刻意脱敏的哪些是忘写了规范感会强很多。4.4 正则写不对才是最大隐患我被坑过的三个 case正则脱敏的坑不在正则本身而在输入数据不符合预期。第一个坑手机号包含 86 或 86 前缀。13812345678能正确匹配但8613812345678会匹配失败最终原样输出等于没脱敏。处理方式是先剥离国家码再执行规则const mobileWithPrefix (v) { const pure v.replace(/^(\?86)/, ); return pure.replace(/^(\d{3})\d{4}(\d{4})$/, $1****$2); };第二个坑邮箱用户名本身只有一两个字符。ab.com按name[0] ***会变成a***b.com看起来没问题但业务上可能要求保持可辨识度更合理的规则是name[0] ***只对长度大于4的用户名生效。第三个坑身份证号里的 X 被误伤。身份证末尾可能是数字也可能是 X正则^(.{6}).{8}(.{4})$的.{4}会捕获123X看起来没问题但如果写成了\d{4}带 X 的身份证就完全无法匹配原样展示出来问题更严重。凡是脱敏规则建议全部用.{n}而不是\d{n}因为脱敏只需要位数信息不需要字符类别信息用\d反而人为制造了匹配失败的条件。5. 数值精度泄露toFixed、BigInt 与浮点运算中的隐形脱敏聊完字符串脱敏我要专门开一节讲一个很多人压根没意识到的问题数值类型的数据在某些操作下会在脱敏后的状态下泄露额外信息。场景是这样的运营看得见的销售额是脱敏后的****但某个排行榜页面需要把销售额参与排序、排名、百分比计算。产品经理要求金额打码但排名要准确。前端拿到的原始数据可能是12345.6789脱敏后显示*****可是在计算排名时如果前端不做处理直接用原始数值参与计算那这个数值就会出现在 JavaScript 的计算中间变量里——打开调试工具逐步断点就能看到。这个不算展示层泄露但它确实把脱敏动作彻底架空了脱敏只是蒙住了用户的眼没有挡住浏览器本身。更隐蔽的是浮点精度问题。电商项目中金额经常以分为单位存储前端拿到1234567分需要转换成12345.67元展示。脱敏需求则是保留前几位后位打码。有人会直接这么写const maskMoney (valueInCents) { const yuan (valueInCents / 100).toFixed(2); return yuan.replace(/^(\d{2})\d/, $1****); };看起来既做了单位换算又做了脱敏但你忘了 JavaScript 的0.1 0.2问题。1234567 / 100在 JS 里的结果不是精确的12345.67而是12345.670000000000072这种带尾巴的浮点数toFixed(2)会四舍五入成12345.67看起来正常可当金额需要参与大量求和、占比计算时浮点误差会累积脱敏后的 **** 位数也会跟着漂移。比如9999999分转成99999.99按保留前两位的规则应该显示99*****但由于浮点误差可能变成10*****——这就不是打码了是算错了。正确处理方式有两个方向方向一金额计算全部走整型分运算只有到最终展示层才做格式化。排序、求和都用分根本不让浮点数出现在业务代码里const sortByAmount (rows) { return rows.sort((a, b) a.amountInCents - b.amountInCents); };方向二如果必须处理浮点数使用BigInt或专门的十进制库。BigInt 适合整型分阶段十进制的场景用decimal.js、bignumber.js这类库避免toFixed之前的隐式精度丢失。import Decimal from decimal.js; const yuan new Decimal(valueInCents).div(100).toFixed(2);还有一个更容易被忽视的坑使用Number类型存储超大 ID。现代项目里订单号、用户 ID 常常是雪花 ID长度超过Number.MAX_SAFE_INTEGER9007199254740991。如果你在脱敏前先做了一步Number(id)然后正则打码后面的几位数字会因为精度丢失变成 0脱敏结果是123456****但真实的 ID 最后四位可能根本不是这个。针对超长 ID一律使用字符串类型处理不要转换。数值与 ID 的脱敏核心原则是脱敏用的原始值必须是无损的。任何可能引入精度变化、位丢失的转换都必须放在脱敏之前的读取原始值阶段就完成而不是边脱敏边转换。6. 最容易翻车的盲区日志、缓存、网络请求与剪贴板6.1 console.log 和 Error 堆栈里的明文这个问题我在评审代码时几乎每次都能碰到页面组件里写了很多console.log(用户信息, userInfo)或者统一封装的logger.error(接口异常, error)这些日志会把完整手机号、身份证号打印到浏览器控制台。控制台对于普通用户不可见但对于任何会用 DevTools 的人来说脱敏页面做的所有工作都是徒劳——直接在 Console 面板搜userInfo就能看到所有原始数据。解决方案不是规定大家别打日志而是做一个日志层脱敏拦截器。统一封装日志函数在写入前递归调用maskDeep// logger.js import { maskDeep } from ./desensitize; const sensitiveKeys [mobile, idCard, bankCard, email, address, token, cookie]; export const safeLog (...args) { const safeArgs args.map((arg) { if (arg instanceof Error) { return { ...arg, stack: maskDeep(arg.stack, [stack]) }; } return maskDeep(arg, sensitiveKeys); }); if (import.meta.env.DEV) { // 开发调试需要保留完整日志 console.log(...args); } else { console.log(...safeArgs); } };这个只在生产环境启用脱敏策略的写法很重要开发时要排查问题不脱敏日志反而好用生产时日志里的敏感数据一旦上传到第三方监控平台就是另一次数据泄露。6.2 缓存与离线包localStorage 里的明文很多前端项目为了让用户下次打开页面更快会把接口数据缓存到 localStorage 或 IndexedDB。如果缓存的原始响应体里包含完整手机号这些数据理论上任何能在浏览器里执行脚本的第三方代码比如被投毒的 CDN 脚本、第三方统计 SDK都能读取到。第三方 SDK 读取 localStorage 是普遍能力不等同于它们会恶意用但风险敞口没必要留。建议是缓存层与展示层分离。页面渲染可以直接从内存数据来但持久化缓存必须走脱敏后的数据或者至少把敏感字段剔除后再写入缓存。如果业务要求下次进入页面时能正常显示完整数据比如编辑场景那就必须设置缓存过期时间与访问控制不能永久缓存敏感数据。6.3 Network 面板接口响应里的明文是否必须保留这是一个比较争议的话题。如果接口返回了完整手机号前端只是不展示那用户打开 DevTools 的 Network 面板依旧能看到mobile: 13812345678。这算泄露吗从平台安全角度算但从业务可操作性角度绝大多数后端接口无法只返回脱敏后且业务可用的数据因为同一份数据需要支持编辑、搜索、校验等场景。合理的折中方案是接口支持按角色和场景下发不同字段集只读场景直接下发脱敏字段编辑场景才下发完整字段并且编辑场景要配套权限校验与水印提示。这样至少能保证打开 Network 面板的人如果本身没有编辑权限看到的数据也是打码后的。6.4 选中复制与右键粘贴数据从页面抽离的通道页面脱敏做得再好用户只要选中文本、CtrlC复制到聊天框脱敏内容就变成了新的可传播数据。这里有两个层面要处理第一禁止复制完整原始数据。监听copy事件对剪贴板内容进行检测如果命中手机号或身份证号的完整正则替换为脱敏版本再写入剪贴板document.addEventListener(copy, (e) { const text window.getSelection()?.toString() || ; const mobilePattern /1[3-9]\d{9}/g; if (mobilePattern.test(text)) { e.clipboardData.setData(text/plain, text.replace(mobilePattern, ***)); e.preventDefault(); } });第二敏感页面上禁止通过剪贴板读取机密数据。比如一个订单备注输入框用户从微信复制了和客户的完整聊天记录进来里面包含手机号保存到后端后手机号就进数据库了。这种输入型泄露前端防不了只能在提交前检测特定字段并进行提示。6.5 iframe 与第三方脚本数据外传的最后一条路径如果页面内嵌了第三方 iframe比如客服聊天组件、数据报表组件或者引入了非自营的第三方脚本这些代码与主页面共享同一份 DOM 和数据上下文某些场景下。即使主页面做了脱敏渲染第三方脚本依然有途径读取内存中的原始数据因为它在同一个 JS 执行环境里。这是前端脱敏无法彻底解决的领域能做的只有尽量使用sandbox属性隔离 iframe 权限第三方脚本通过 SRISubresource Integrity锁定内容完整性对高风险页面定期做第三方依赖审计你会发现这节的内容已经超出脱敏本身进入了前端数据最小暴露的范畴。这是必然的脱敏只是手段不泄露才是目的。7. 脱敏与搜索、排序、打印的冲突加盐字段与双轨数据模型脱敏做多了以后你会遇到一个矛盾脱敏是为了隐藏信息但业务需要信息参与搜索、排序、去重、导出。最典型的例子是订单列表按手机号搜索如果列表显示的是138****5678用户输入完整手机号搜索时前端要么搜不到因为列表里根本没有完整号码要么搜到了但页面展示的跟输入的不匹配让用户怀疑数据错了。解决这个矛盾的标准方案是双轨数据模型数据对象里同时保留脱敏后展示字段和原始值字段但原始值字段只能被业务逻辑使用不能被模板直接渲染。拿订单数据举例// 订单数据模型 const orderRow { id: 20240101123000123, customerNameRaw: 张三, customerNameMasked: 张*, mobileRaw: 13812345678, mobileMasked: 138****5678, searchableText: 13812345678 张三 20240101123000123, };模板里一律使用mobileMasked搜索时用searchableText匹配排序时如果要按手机号排用mobileRaw做 sort 函数入参但即使排出来了页面上每一行仍然展示mobileMasked。这样搜索、排序功能不退化展示安全性也不降级。另一种思路是加盐脱敏常用于 ID 类字段。比如订单号20240101123000123脱敏后显示20240101********3但搜索、导出时需要稳定识别这个订单。做法是脱敏时保留一个不可逆的加盐哈希字段用于精确查询import { createHash } from crypto; const maskIdWithSalt (rawId, salt project-secret-key) { const masked rawId.replace(/^(.{8}).(.{1})$/, $1****$2); const hash createHash(sha256).update(rawId salt).digest(hex).slice(0, 8); return { masked, hash }; };加盐哈希的定位是供内部精确匹配用的不能作为展示依据也不建议暴露给客户端做图灵测试否则等于提供了破解完整 ID 的枚举通道。打印与 PDF 导出是另一个高频冲突场景。业务系统的快递单打印、合同打印需要完整的联系人和地址但屏幕展示时需要脱敏。这个场景没有两全其美的做法只能通过权限和审计来兜底打印操作必须记录操作人、时间、打印内容快照且打印页面的 URL 不能携带完整数据而是通过一次性 token 换取打印数据。我在实践中的体会是脱敏方案的复杂度不取决于你要隐藏多少数据而取决于业务需要在信息隐藏和信息使用之间走多远。如果只是列表展示第4节的工具函数就够如果涉及搜索、排序、打印、导入导出就必须引入双轨模型把这些能力显式地设计进数据层而不是在渲染层临时补丁。8. 落地实践给团队的一套脱敏接入清单与验收标准写这类文章最怕只讲概念不讲落地所以我最后把方法论收敛成一份可以直接拿去用的接入清单。这套清单我在多个中后台项目里用过按它的顺序推进基本不会出现漏脱敏和误脱敏的大问题。8.1 第一阶段数据盘点1-2天挨个页面梳理所有接口返回的字段建立敏感字段清单。不要只看当前页面上显示了哪些字段要看整个请求响应体里有哪些字段。很多接口是多用途的返回了页面不需要的完整手机号这种也要记下来。梳理完成后按页面敏感度分三级P0身份证、银行卡、完整家庭住址、健康数据P1手机号、邮箱、详细地址、车牌号、企业税号P2姓名、昵称、大致区域、IP 地址8.2 第二阶段规则沉淀0.5-1天根据业务需求为每一类字段确定脱敏规则。这里给定一个通用配置参考字段场景A只读展示场景B可编辑场景C打印/导出手机号138****5678聚焦时明文失焦打码明文需权限审计身份证前6后4不支持前端编辑脱敏后打印银行卡前6后4不支持前端编辑脱敏后打印邮箱首字母***明文明文需审计地址到区县级明文明文需审计8.3 第三阶段编码实施2-5天按照我前面提到的四道防线评估当前项目哪些阶段缺失后端接口是否已经按场景返回最小字段集前端是否已有统一的脱敏工具库与深度遍历能力敏感页面是否在渲染层强制使用脱敏组件或指令是否配置了水印、复制度与日志脱敏如果一个阶段缺失优先补哪个我的建议是先补第3道防线渲染层因为改动最小、见效最快再补第4道防线日志/水印因为风险最隐蔽最后是第2道请求层拦截因为容易误伤业务逻辑。第1道防线后端字段裁剪配合后端一起排期推进。8.4 第四阶段验收标准光写完代码不算完给一份可执行的验收 checklist[ ] 直接访问接口响应检查 Network 面板里 P0 级字段是否只在有权限的场景返回明文[ ] 打开 Console搜索手机号、身份证号关键词确认页面不打印任何原始值[ ] 尝试全选复制页面文本粘贴到任意文档中确认脱敏字段不还原[ ] 对页面进行截图确认水印覆盖所有敏感信息区域[ ] 对列表进行排序、搜索操作验证脱敏后字段仍然支持这些业务能力[ ] 对表格执行导出、打印检查导出内容中的敏感信息是否经过了权限校验[ ] 用无痕模式或新用户身份访问页面确认本地缓存中不含敏感原始字段8.5 关于脱敏后如何还原的最后一个提醒必须明确一点前端不应该具备可逆脱敏能力。有些团队为了实现点击查看完整手机号在前端保存了一张脱敏规则与还原规则的映射表这就等于把密钥放在了攻击者手中。前端最多能做到点击后向后端申请一次性查看权限后端鉴权通过后返回完整字段前端在短暂时间窗口内展示且这个行为必须被后端记录。我在实际项目里见过最离谱的写法一个restoreMask函数接收138****5678和678两个参数就能拼回完整手机号。这个函数如果被逆向了整个脱敏体系直接归零。所以前端脱敏必须是不可逆的单向过程任何还原动作都必须由服务端完成并配套审计。最后我从个人经验角度多提一句前端脱敏这件事投入产出比其实很高。写一个通用工具库加上组件封装大概一两天工作量但它能在项目生命周期里持续兜底。真正要花心思的不是代码而是和产品、后端对齐哪个字段在哪个场景下允许出现明文这个规则。规则清楚了代码只是翻译。

相关推荐

XSS跨站脚本攻击实战:Xss-Labs前十关通关思路与绕过技巧
XSS跨站脚本攻击实战:Xss-Labs前十关通关思路与绕过技巧

XSS(跨站脚本攻击)这个概念,我当年看书看了三遍都没彻底转过弯来,直到把Xss-Labs靶场前十关一关一关踩过去,才真正理解什么叫"用户输入不可信、输出不编码"。这个靶场是专为XSS入门设计的本地训练环境&#… · 2026/9/25 13:39:50

TiddlyWiki5 历史数据迁移实战:用 Console 脚本与 Shell 管道将旧 HTML 访谈站点导入为 Wiki Edition
TiddlyWiki5 历史数据迁移实战:用 Console 脚本与 Shell 管道将旧 HTML 访谈站点导入为 Wiki Edition

前端后端 【免费下载链接】TiddlyWiki5 A self-contained JavaScript wiki for the browser, Node.js, AWS Lambda etc. 项目地址: https://gitcode.com/gh_mirrors/ti/TiddlyWiki5 点击查看 免费下载 本篇技术指南围绕 TiddlyWiki5 仓库中 tiddlywiki-surveys 这一… · 2026/9/25 13:39:31

大模型 VS Agent:别再搞混了!用 TaoToken 统一 Key 打通 AI 工具链,让 Agent 真正“活”起来
大模型 VS Agent:别再搞混了!用 TaoToken 统一 Key 打通 AI 工具链,让 Agent 真正“活”起来

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 13:39:25

自托管CRM实战:从永久在线到数据自主的团队协作方案
自托管CRM实战:从永久在线到数据自主的团队协作方案

1. 从“永久在线”到“数据归属”:DeskcommCRM 到底解决什么问题做 CRM 这些年,我一直有个很深的体会:大部分团队不是不需要客户管理,而是被“CRM 太贵、太复杂、太被动”这三座大山劝退了。市面上的 SaaS CRM 按年付费&#xff0… · 2026/9/25 14:20:36

年夜饭、过年用酒怎么选?聊聊黄酒在传统年节里的角色
年夜饭、过年用酒怎么选?聊聊黄酒在传统年节里的角色

年夜饭是中国人一年中最隆重的一顿饭,桌上的酒也不只是助兴,更带着团圆、辞岁、迎新的意味。在不少地方,黄酒本就是年节里的老传统。这篇聊聊黄酒为什么适合过年,年夜饭菜式该怎么配酒,以及全家老少同席时怎么安排。 一… · 2026/9/25 14:20:36

中秋家宴喝什么?黄酒配月饼与大闸蟹的完整建议
中秋家宴喝什么?黄酒配月饼与大闸蟹的完整建议

中秋是一年里最讲究“团圆”的一顿饭:一家人坐齐,桌上有月饼、有应季的菜,很多家庭还少不了大闸蟹。家宴喝什么酒,其实很有讲究。这篇给一套以黄酒为核心的中秋饮酒方案,包括配蟹、配月饼分别怎么喝,以及老… · 2026/9/25 14:20:29

15KW永磁同步电机双闭环PI控制Simulink仿真实践
15KW永磁同步电机双闭环PI控制Simulink仿真实践

接到一台15KW永磁同步电机的控制系统设计任务时,我身边不少同事的第一反应是直接打开Simulink拖模型——这其实是最容易翻车的开法。双闭环PI控制本身不复杂,但真正决定项目成败的,往往是建模前的参数核算、PI整定里的工程约束,以… · 2026/9/25 14:20:29

Linux蓝牙音频实战:BlueZ下A2DP、AVRCP与HFP-HF全链路配置与调试
Linux蓝牙音频实战:BlueZ下A2DP、AVRCP与HFP-HF全链路配置与调试

蓝牙音频这块在Linux上一直是个让人又爱又恨的话题。爱的是BlueZ这套协议栈确实完整,A2DP、AVRCP、HFP该有的profile一个不少;恨的是配置起来坑太多,尤其是HFP-HF这块,很多人卡在SCO链路上死活出不来声音。我前后在几个不同的硬件… · 2026/9/25 14:20:23

Category B与Category 1/2/3/4分类体系解析:选型、认证与避坑指南
Category B与Category 1/2/3/4分类体系解析:选型、认证与避坑指南

1. 从一次被问懵的经历说起前阵子有个刚入行的朋友拿着几份产品规格书来问我,说客户在选型的时候反复提到“Category B”和“1、2、3、4”这几个词,他翻遍了手头的资料,发现不同厂家给的说明还不太一样,有的把B单独拎出来讲&#… · 2026/9/25 14:20:17

数值优化(Numerical Optimization)学习系列-03-共轭梯度方法(Conjugate Gradient)
数值优化(Numerical Optimization)学习系列-03-共轭梯度方法(Conjugate Gradient)

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:31

创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战
创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:31

MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX
MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:37

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码