news 2026/9/19 20:13:03

Web前端脱敏实战:从工具函数到框架集成的防泄露指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Web前端脱敏实战:从工具函数到框架集成的防泄露指南

先说一个我去年遇到的真实事故。我们公司后台的客户列表页,一个客服同事要把一张订单截图发给客户核对信息,结果手滑把截图发到了客户群里。那张截图里,客户的手机号、家庭住址、身份证号全部是明文,清清楚楚。当天下午就有客户打电话来投诉说接到了诈骗电话。复盘的时候,后端同事说“接口是按规范返回的明文,脱敏不是后端的事”,前端同事说“框架是统一模板,没接入脱敏组件,压根没想过”。两边都有道理,但问题就卡在中间:Web前端脱敏这件事,理论上该做,实际却常常没人做。

这事儿之后我花了两周时间,把自己的前端脱敏方案、工具函数和框架集成方式彻底做了一遍梳理和封装。这篇文章就是那套方案的完整记录,覆盖场景判断、方法选型、代码实现、踩坑排查,主要面向日常做Web前端开发的同学,也适合全栈工程师和关注数据隐私的产品经理参考。

1. 先搞清楚:脱敏这件事到底该谁做、在哪儿做

很多项目里,脱敏是“大家都觉得该做,但没人认领”的灰色地带。不把这个问题先说透,后面的代码写得再漂亮也落不了地。

1.1 什么时候必须在前端脱敏

后端接口返回明文数据,前端拿到后直接渲染,这是大多数后台管理系统最原始的形态。问题在于,前端页面是数据流向用户的最后一公里——只要页面里出现了明文,它就可能在浏览器开发者工具、截图、录屏、客服聊天记录里“二次泄露”。

我做了一个相对武断但有效的判断标准:只要页面会被非开发人员(客服、运营、管理层、客户本人)看到,就必须做前端脱敏。

原因是后端脱敏往往针对的是“接口级”的防护,它保证的是传输链路和存储环节的合规。但前端页面一旦渲染出明文,数据就在用户的浏览器里“落地”了。这时候即使后端接口脱敏做得再好,也防不住一张截图外传。举个极端例子:就算后端把手机号改成138****8000,前端如果从 localStorage 里读到一份缓存数据重新渲染,照样能把明文展示出来——前端脱敏解决的就是这类“展示层”的泄露。

1.2 前端脱敏和后端脱敏的分工边界

很多人问:既然后端能做脱敏,为什么还要前端做?这不是重复建设,而是两道不同关卡。

后端脱敏解决的是“数据离开服务端时,能不能少暴露”。适合做持久化存储前的加密、接口返回前的规则化处理、日志脱敏。前端脱敏解决的是“数据到达浏览器后,怎么展示才不泄露”。适合做列表字段掩码、详情页敏感信息收起、导出文件前二次打码、错误日志脱敏。

我的建议是:后端把接口层的基础脱敏做好(比如手机号自动打星号),前端把展示层的兜底脱敏做好(比如针对后端漏配的字段、缓存数据、联调环境数据)。后端为主、前端兜底,谁也别替谁背锅。

有一个典型的反面案例:某个系统要求后端对列表接口的身份证号直接脱敏,但是“详情页”为了后续编辑功能,后端返回的是完整身份证号。前端列表页跳详情页时,直接把列表里的脱敏字段替换成详情接口的明文数据,导致列表页也变成明文。这就是前后端职责没有对齐造成的——后端以为列表页不展示明文,前端以为详情页可以复用明文,结果中间漏了一道。

1.3 前端脱敏必须守住的3条原则

原则一:脱敏不是加密。脱敏产生的138****8000是不可逆的展示层结果,不是用来还原的。如果业务上需要“查看完整手机号”,应该调用权限受控的详情接口,而不是在前端对脱敏结果做反向解析。我自己见过有同事写了个restoreMask函数,企图把脱敏数据还原成明文,这种设计本质上是把脱敏当摆设。

原则二:脱敏要统一规则,不能一组件一写法。同一个手机号,列表页显示138****8000,详情页显示138****8000,导出文件却显示138-****-8000,用户和客服都会蒙圈。所有端(包括管理后台、H5、小程序)的脱敏规则必须由一份公共配置维护。

原则三:面向场景脱敏,不面向字段脱敏。同一个字段在不同场景展示规则可以不同。比如订单详情页,机主本人查看时手机号可以明文(因为这就是他本人的手机号),但客服查看时必须脱敏。所以脱敏组件最好支持“权限模式”和“场景模式”两种配置,而不是写死某个字段必须脱敏。

2. 最常见的脱敏场景和对应的处理方案

前端脱敏不是简单“打星号”三个字能概括的。不同场景的脱敏点、保留位数、掩码符号都有讲究,而且很多脱敏点的位置很隐蔽,容易被忽略。

2.1 列表页与详情页的字段级脱敏

列表页是最典型的脱敏场景。几类高频字段的规则我做成了固定配置:

字段类型示例明文推荐脱敏结果规则说明
手机号13812348000138****8000前3后4保留,中间4位掩码
身份证号110101199003073333110***********3333前6后4保留,其余掩码
银行卡号62220212345678901236222 **** **** 0123前4后4保留,中间分段掩码
邮箱zhangsan@example.comz****n@example.com保留首字符和@前末字符,或统一掩码
姓名张三丰张**复姓做单独处理
地址北京市朝阳区望京街道xx小区xx号楼北京市朝阳区********保留省市区,街道后掩码

详情页和列表页略有不同。详情页数据量小、展示空间大,我更喜欢采用“点击可见、离开隐藏”的交互式脱敏。用户点击“眼睛”图标时,调用权限接口拿到明文,并记录操作日志,3秒后自动隐藏,同时触发浏览器的自动截图防泄露策略(部分内网环境可以启用)。这种方案比单纯打星号体验好,又比一直展示明文安全。

身份证号脱敏特别容易出错,因为它有两种常见情况。18位身份证前6位是地址码,所以我强烈建议保留前6后4,展示110101********3333。如果只保留前3后4,等于把省市区的前缀信息也暴露了一部分;如果保留前4后4,地址码泄露更多。实测过很多库,/^(.{6}).*(.{4})$/搭配'$1********$2'是最稳妥的写法。

2.2 操作类文案与第三方集成的脱敏点

这是最容易被忽略的脱敏盲区。

第一大类是“页面文案模板拼接”的泄露。比如订单状态栏里写“您的手机号 13812348000 已通过实名认证”,这种字符串在服务端模板拼接好后,接口直接以notice字段下发,前端根本没机会对手机号单独处理。所以我在前端加了一道“敏感信息扫描器”,可以简单理解为一个正则匹配 + 字符串替换的工具,在渲染前对接口返回的字符串字段做一次全量扫描,命中手机号、身份证号等模式时自动打码。

第二大类是第三方脚本的埋点上报。很多项目接入了行为埋点、错误监控、客服系统。我踩过的最大的坑是:列表页明明做了脱敏,但埋点 SDK 自动采集 DOM 文本,明文数据被带到了埋点服务器。所以前端接入第三方组件时,要么关闭 DOM 自动采集,要么在埋点上报前先对 payload 里的text字段做过滤脱敏。

第三大类是分享和复制功能。比如用户“复制订单信息”后,剪贴板里是全量明文(因为navigator.clipboard.writeText拿到的是完整文本)。这类功能如果非做不可,至少要把复制内容的敏感字段打码,否则前端脱敏做得再好,复制一下就全破功了。

2.3 打印、导出、分享前的脱敏处理

后台系统常遇到“打印快递单”“导出客户表单”的需求。如果打印走的是window.print(),浏览器渲染的是当前 DOM,打印预览里出现明文就说明你的 DOM 节点里有明文数据。处理方式是打印前遍历打印区域内的敏感 DOM 节点,把文本内容批量替换成脱敏值,打印完成后恢复。

导出 Excel 的场景更麻烦。多数前端导出方案是“读取表格组件的当前数据”,也就是说表格 columns 里如果是脱敏后的138****8000,导出文件自然是脱敏的。但如果你用“从接口重新拉全量数据再导出”的方案,就可能把明文写进文件里。我自己处理时统一在导出方法里套了一个脱敏过滤函数,导出的字段全部走一遍desensitizeField(columns, row)

分享链接和海报是另一个高频泄露路径。比如活动页的分享海报里嵌入了用户邀请码,邀请码虽然不是严格意义的 PII(个人隐私信息),但结合用户手机号就能反查身份。安全起见,我认为任何分享类文案里的“身份标识符”都应当做“短态化”脱敏——比如只展示前2位 + 星号 + 后1位,或者干脆只展示一个随机生成的短码,而不是直接分享完整 ID。

3. 手写一套可复用的脱敏工具函数

上面讲了半天“怎么做”,接下来直接上代码。我提供了一套适合直接抄作业的脱敏工具函数,覆盖常用字段,附带通用规则配置,同时也谈一下我在性能和组件化上的取舍。

3.1 工具函数设计:纯函数、单一职责、可测试

我封装的核心理念是“正则优先、纯函数优先”。每个脱敏函数接收valueoptions,返回脱敏后的字符串,不依赖任何框架和 DOM,这样单元测试写起来非常舒服,也可以直接移植到 Node.js 端做服务端兜底。

项目里我建立了一个mask.js工具文件:

const DEFAULT_MASK_CHAR = '*'; function maskValue(value, start, end, maskChar = DEFAULT_MASK_CHAR, replaceCount) { if (value === null || value === undefined) return ''; const str = String(value); if (str.length <= start + end) { // 字符串太短时,退化为全掩码 return str.replace(/./g, maskChar); } const head = str.slice(0, start); const tail = str.slice(-end); const len = replaceCount || (str.length - start - end); const masked = maskChar.repeat(len); return `${head}${masked}${tail}`; } function maskPhone(phone, { maskChar = DEFAULT_MASK_CHAR } = {}) { if (!phone) return ''; const str = String(phone); if (str.length !== 11) return maskValue(str, 3, 4, maskChar); return `${str.slice(0, 3)}${maskChar.repeat(4)}${str.slice(7)}`; } function maskIdCard(idCard, { maskChar = DEFAULT_MASK_CHAR } = {}) { if (!idCard) return ''; const str = String(idCard); if (str.length !== 18) return maskValue(str, 6, 4, maskChar); return `${str.slice(0, 6)}${maskChar.repeat(8)}${str.slice(14)}`; } function maskBankCard(cardNo, { maskChar = DEFAULT_MASK_CHAR } = {}) { if (!cardNo) return ''; const str = String(cardNo).replace(/\s/g, ''); if (str.length < 9) return maskValue(str, 4, 4, maskChar); return `${str.slice(0, 4)} ${maskChar.repeat(4)} ${maskChar.repeat(4)} ${str.slice(-4)}`; } function maskEmail(email, { maskChar = DEFAULT_MASK_CHAR, visibleHead = 1, visibleTail = 1 } = {}) { if (!email || !email.includes('@')) return email || ''; const [name, domain] = email.split('@'); if (name.length <= visibleHead + visibleTail) { return `${maskChar.repeat(name.length)}@${domain}`; } const head = name.slice(0, visibleHead); const tail = name.slice(-visibleTail); return `${head}${maskChar.repeat(name.length - visibleHead - visibleTail)}${tail}@${domain}`; } function maskName(name, { maskChar = DEFAULT_MASK_CHAR } = {}) { if (!name) return ''; const str = String(name); if (str.length === 1) return maskChar; if (str.length === 2) return `${str[0]}${maskChar}`; return `${str[0]}${maskChar.repeat(str.length - 1)}`; } function maskAddress(address, { maskChar = DEFAULT_MASK_CHAR, keepLevels = 2 } = {}) { if (!address) return ''; const str = String(address); const match = str.match(/^(.*?[省市区县])(.*)$/); if (!match) return maskValue(str, Math.ceil(str.length / 2), 0, maskChar); if (keepLevels === 2) { // 保留到区县,后面全部掩码 return `${match[1]}${maskChar.repeat(match[2].length)}`; } return `${maskChar.repeat(match[1].length)}${match[2] ? maskChar.repeat(match[2].length) : ''}`; } module.exports = { maskValue, maskPhone, maskIdCard, maskBankCard, maskEmail, maskName, maskAddress, };

有两点使用心得说下。

一是银行卡号我保留了空格和4位分段,这是为了可读性。如果不做分段,622202****5678用户很容易看错,对账时很麻烦。

二是邮箱脱敏参数里visibleHeadvisibleTail我默认都是1。有隐私洁癖的团队也可以设置成visibleHead: 2, visibleTail: 0,用户名的第2个字符也掩码,泄露风险更低。但用户体验会略差,毕竟用户需要确认“这是我的邮箱”,保留太多反而不好认。

3.2 敏感信息通用扫描器

除了给字段逐个脱敏,实际项目还需要处理“接口返回了一段包含了手机号和身份证号的文本”的情况,比如“您预留的手机号13812348000与证件号110101199003073333不一致”。

我的方案是维护一个“敏感正则列表”,在文本渲染前做一次全局替换:

const sensitivePatterns = [ { name: 'phone', pattern: /(1[3-9]\d)\d{4}(\d{4})/g, replace: '$1****$2' }, { name: 'idCard', pattern: /(\d{6})\d{8}(\d{4})/g, replace: '$1********$2' }, { name: 'bankCard', pattern: /(\d{4})\d{10,12}(\d{4})/g, replace: '$1 **** **** $2' }, ]; function desensitizeText(input) { if (typeof input !== 'string') return input; let result = input; for (const item of sensitivePatterns) { result = result.replace(item.pattern, item.replace); } return result; }

这个扫描器我放在所有接口响应拦截器的出口处。前提是配置里打开autoDesensitizeText: true,全局对res.data做一次遍历,遇到字符串字段就执行desensitizeText。平时它不会把用户的自定义文本(比如备注)误伤,因为它只匹配手机号和身份证号样式的字符串。

性能方面,全量遍历接口响应对大列表有影响,但实测下来 1000 条数据、每条 20 个字段,耗时约 3~5ms,在可接受范围。如果列表数据特别大,可以只对descriptionnoticeremark这类字符串字段做扫描,不做全字段扫描。

3.3 组件化与框架接入:Vue自定义指令和React Hook

工具函数只是“弹药”,关键还得接入组件库。

Vue 项目里我用自定义指令v-mask来做,避免每次手写maskPhone(user.phone)。实现思路是:指令的binding.value接收{ type: 'phone' | 'idCard' | 'email' | 'name' | 'address', options? },在mountedupdated钩子里对el.textContent做替换:

// v-mask.js import { maskPhone, maskIdCard, maskEmail, maskName, maskAddress } from './mask'; const maskers = { phone: maskPhone, idCard: maskIdCard, email: maskEmail, name: maskName, address: maskAddress, }; const vMask = { mounted(el, binding) { if (!binding.value) return; const { type, options } = binding.value; const masker = maskers[type]; if (!masker) return; // 只处理文本节点,避免误伤子节点里的按钮等文本 const originalText = el.textContent; const maskText = masker(originalText, options); el.textContent = maskText; el.dataset.originText = originalText; el.dataset.masked = 'true'; }, updated(el, binding) { if (!binding.value) return; const { type, options } = binding.value; const masker = maskers[type]; if (!masker) return; const currentText = el.textContent; // 如果当前文本已经脱敏了,不能二次脱敏 if (el.dataset.masked === 'true') return; const maskText = masker(currentText, options); el.textContent = maskText; el.dataset.originText = currentText; el.dataset.masked = 'true'; }, unmounted(el) { // 组件卸载时恢复原始文本,避免复用节点时污染 if (el.dataset.originText) { el.textContent = el.dataset.originText; } }, }; export default vMask;

这里有个细节很容易踩坑:如果渲染函数在指令更新前已经把数据换掉了,updated里就不能直接拿currentText当原文本再脱敏一次,否则138****8000会变成1**80**所以我设计了一个dataset.masked标记,只对未脱敏的原始文本做一次处理。

React 项目我通常写一个useDesensitizedData的 Hook,对列表数据做一层映射,再传入组件渲染:

import { useMemo } from 'react'; import { maskPhone, maskIdCard, maskEmail, maskName } from './mask'; function useDesensitizedData(data, config = {}) { return useMemo(() => { if (!Array.isArray(data)) return data; return data.map((item) => { const result = { ...item }; Object.keys(config).forEach((field) => { const type = config[field]; const value = result[field]; if (value === null || value === undefined || value === '') return; switch (type) { case 'phone': result[field] = maskPhone(value); break; case 'idCard': result[field] = maskIdCard(value); break; case 'email': result[field] = maskEmail(value); break; case 'name': result[field] = maskName(value); break; default: break; } }); return result; }); }, [data, config]); } export default useDesensitizedData;

使用示例:

const maskedList = useDesensitizedData(userList, { phone: 'phone', idCard: 'idCard', email: 'email', name: 'name', });

选型建议是:Vue 项目优先用自定义指令,因为指令是声明式的,模板里一眼能看出“这个字段做了脱敏”;React 项目优先用 Hook 或高阶组件,因为 React 的数据流是单向的,在数据层做映射比在渲染层做遍历更可控。如果你用的是组件库的 Table 组件,还有一种方案是统一在columnsrender里套一层脱敏函数,但这种方式维护成本高,表格改了列配置还要检查脱敏逻辑有没有跟上。

3.4 动态数据的脱敏时机与性能优化

脱敏的“时机”很关键。有人喜欢在接口then里做脱敏,有人喜欢在渲染时做脱敏。我推荐“数据进入业务层之前脱敏”,也就是组件拿到原始数据后、setState/reactive之前就处理好,这样渲染层拿到的一定是脱敏后的数据,杜绝“渲染一半又跳成明文”的闪烁。

性能方面,大列表脱敏最大的瓶颈在于String.replace在长字符串上的匹配。比如地址脱敏,我见过有的实现用正则/.{2}$/匹配城市名,在大列表 10000 条记录时耗时上百毫秒。优化方案有两个:

  1. 对脱敏函数做记忆化(memoize),同一个原始值只脱敏一次,缓存结果;
  2. 把脱敏时机挪到虚拟滚动渲染的“可视区内”,用户滚到哪一屏,只处理哪一屏的数据,用户感知不到处理耗时。

我自己的经验是:列表超过 500 条就用虚拟滚动 + 可视区脱敏,不要全量脱敏。前端脱敏的目的是“防泄露”,不是“防后端”,可视区外的数据不渲染也谈不上泄露,等滚动到可视区时再脱敏即可,性能和安全能取得一个比较好的平衡。

4. 实操中的常见问题与排查技巧实录

脱敏方案上线后,问题不会比功能上线前少。这一节我把碰到的典型问题、排查思路和解决办法整理成一个速查表,再挑几个高发问题展开讲。

4.1 常见问题速查表

问题现象根因解决方案
脱敏后的数据在页面刷新后变回明文缓存/状态管理里存的是明文在接口响应拦截器统一处理,并检查 localStorage/sessionStorage
某个字段脱敏后,搜索/筛选失效前端用脱敏后的字段值请求后端脱敏只做展示层,请求参数必须用原始数据;两套数据分开存
分享出去的链接重新打开出现明文分享页用了同一套代码但没启用脱敏开关增加场景配置,分享页强制开启脱敏
复制表格数据,剪贴板里是明文剪贴板写入的是 DOM 原始文本复制事件里拦截并替换成脱敏文本
打印预览出现明文打印用的 DOM 节点和展示用 DOM 节点不同打印前遍历目标区域做文本替换
脱敏数据被二次脱敏,变成1**80**指令的 updated 钩子对已脱敏文本重复处理检查 dataset.masked 标记或改用保障函数
email 中@后也被打码正则没排除域名部分先 split('@') 再处理用户名,域名不脱敏
日志里出现明文手机号前端 utils 日志打印了入参对象日志打印前调用 desensitizeText 过滤;或在 console 封装层统一过滤

4.2 脱敏后数据不可逆导致的线上问题

最典型也最头疼的:详情页的“编辑”功能。用户点“编辑”后,表单里回显的是脱敏后的138****8000,用户不改这个字段,直接提交,前端把脱敏值传给了后端,后端就把客户手机号真的改成了138****8000,酿成事故。

解决方案是“展示脱敏 + 提交原始”分开双轨。表单详情页面用脱敏后的数据展示,但提交时从详情接口的原始数据快照里取值,不依赖输入框的展示文本。具体做法是:打开编辑弹窗的同时请求详情接口,拿到明文后存到ref/state中;表单里的手机号字段展示脱敏值且禁填;提交时优先使用快照里的明文;如果用户必须修改手机号,则走单独的验证码校验流程,而不是直接传 input 值。

这个坑我至少见三个项目踩过。前端脱敏不能只考虑“怎么把数据藏起来”,还得考虑“藏起来之后业务怎么继续”,否则脱敏会成为数据更新链路上的定时炸弹。

4.3 脱敏规则和后端不一致,前端该怎么兜底

后端做了一套脱敏,前端也做了一套脱敏,两边规则一旦不一致,前端拿到的就是“已经脱敏过”的数据,再用自己的规则去脱敏,就会出现1**80**这种恶性结果。这个问题在前后端并行开发时特别容易出现。

我给团队定的规矩是:前后端各自维护一份脱敏规则声明,接口文档里明确标注每个字段的脱敏级别(L1明文、L2中间脱敏、L3完全脱敏),前端优先信任接口返回的数据,如果发现数据已经脱敏(通过正则检测是否含*或其他掩码字符),就不再做二次处理。

关键代码实现:

function isMasked(value) { return typeof value === 'string' && /[*#xX]/.test(value); } function safeMask(value, masker) { if (!value || isMasked(value)) return value; return masker(value); }

这样即使前后端规则不一致,前端也能通过“识别已脱敏”来兜底,不会破坏数据。另一个兜底方案是:前端给后端传递原始数据时,约定所有展示字段用xxxDisplayxxxOrigin双字段,展示字段可能来自前端脱敏或后端脱敏,原始字段仅用于提交和操作日志,前端永不展示xxxOrigin

4.4 日志、错误上报和第三方的“隐性泄露”

前面提过埋点 SDK 自动采集 DOM 文本的问题,这里再展开一次,因为这是我最想让同行们重视的隐性通道。

很多前端团队脱敏做得不错,列表页、详情页都很干净,但打开浏览器 Console 一看:接口响应的 log 里全是明文;错误监控平台上报的componentStackpropsstate里全是明文;客服系统接入的网页里,客服看得到当前用户的所有页面数据。

排查建议我从三方面展开:

  1. console 层面:不推荐全局重写console.log(影响调试),但可以在axios拦截器里对“开发环境下的打印日志”做一次脱敏。生产环境的调试日志应直接关闭,或者白名单化。

  2. 错误上报层面:接入 Sentry 这类平台时,在beforeSend回调里把error对象序列化后走一遍desensitizeText,再把脱敏后的内容上报。不要为了方便排查 bug 就把整个 state 原样塞进上报 payload。

  3. 第三方客服/在线聊天层面:如果第三方脚本要采集当前页面信息,尽量关闭“全页面自动采集”,只上报主动埋点的点击事件。这个属于供应商配置问题,需要在接入时确认好隐私条款和采集范围。

有个反面教训是:我们曾上线一套新的错误监控 SDK,本意是捕获前端异常,结果它把 VNode 的 props 全都序列化上报了,包含列表页所有用户的明文手机号。等发现时已经上报了几天,导致客户的隐私数据在企业外部服务器留存。前端做脱敏时,这类“非页面直接展示但被第三方间接采集”的泄露路径必须同步排查。

5. 落地推动:怎么让团队真正执行脱敏规范

工具和函数都写了,如果团队不执行、不维护,这套方案很快就会变成一次性交付的垃圾代码。我总结了几个在团队里推动落地有效的做法,分享给大家。

第一个做法是把脱敏规则收口成“配置中心化”,而不是散落在各个业务代码里。我在项目里维护一个desensitize.config.js配置文件,团队的脱敏规则、字段映射、场景开关全部集中在这里。业务组件直接引配置,不写死规则,后续规则变更只改这一个文件。这样评审代码时,看到有人直接replace(/^(\d{3})\d{4}(\d{4})$/, '$1****$2')在业务代码里硬编码,就能快速揪出来。

第二个做法是在 ESLint 或 Code Review 清单里加一条“展示层敏感字段必须走脱敏工具”。具体到实操,我给团队加了一个简单脚本,扫描代码库中 “手机号”“身份证”“银行卡”相关的column定义,如果没有包在desensitize相关函数中,就在 MR 时提示告警。规则不复杂,但能在代码合并前发现问题,比事后线上事故好处理太多。

第三个做法是建立“线上泄露自查”机制。前端脱敏做得再好,也不代表页面 100% 没有明文。我的自查方法是:在后台系统里设置一个只读的“隐私测试账号”和一批测试数据,每天定时用 Puppeteer 自动化打开关键页面,抓取页面 DOM 文本,正则扫描是否出现明文手机号、身份证号、银行卡号。一旦命中,立即告警并定位到页面路由。这个方法成本不算高,比人工检查可靠得多,强烈建议在用户量较大的系统里上。

如果你所在项目还没有任何脱敏措施,我的建议是先别一上来就搞全套工具库和自动化脚本。先挑一个最高频的数据出口——通常是列表页和详情页的手机号加姓名——用最朴素的replace函数做掉,观察一周,再逐步扩展字段和场景。脱敏这件事,最怕的不是做不完善,而是宁可等到事故发生也不愿意开始。

根据我自己的经验,Web前端脱敏做得好不好,本质上是团队有没有把“隐私保护”当成前端工程的一部分来对待。技术难度不算高,真正难的是让每个前端同学在写每一行渲染代码时,都下意识地问一句:这个字段脱敏了吗?这篇文章里的工具函数和方案可以随时抄,但只有把脱敏意识渗透进开发习惯里,数据泄露这种事才能真正远离你。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/19 20:11:26

Textual ListItem 详解:构建 ListView 列表项的核心组件

Textual ListItem 详解&#xff1a;构建 ListView 列表项的核心组件 【免费下载链接】textual The lean application framework for Python. Build sophisticated user interfaces with a simple Python API. Run your apps in the terminal and a web browser. 项目地址: ht…

作者头像 李华
网站建设 2026/9/19 20:11:16

循迹小车从检测到控制:红外对管、差速PWM与PID调参全解析

简介&#xff1a;围绕P89V51RB2单片机的循迹小车实验报告&#xff0c;是一份面向电气工程与自动化学院学生的课程设计实践资料&#xff0c;完整展示了从系统总体设计、硬件电路搭建到软件驱动编写与整机调试的全过程。报告以三轮小车为平台&#xff0c;讲解红外探测法识别黑线、…

作者头像 李华
网站建设 2026/9/19 20:10:04

用 D435i 做室内避障:从接线到跑通的快速路径

用 D435i 做室内避障&#xff1a;从接线到跑通的快速路径 【免费下载链接】librealsense RealSense SDK 项目地址: https://gitcode.com/GitHub_Trending/li/librealsense 场景很具体&#xff1a;机器人向前开&#xff0c;前方桌角离它 60 厘米&#xff0c;你得拿到一个…

作者头像 李华