前端项目中敏感信息的安全处理与实战避坑指南
为啥要折腾敏感信息的前端处理?
最近在做几个移动端项目,用户登录、支付、个人资料这些功能都涉及敏感信息。说实话,一开始我也没太在意,觉得“不就是把手机号、身份证号显示出来嘛”,直到有一次测试说“你这页面后退还能看到上一个用户的手机号”,我才意识到:前端对敏感信息的处理,真不是加个星号那么简单。
于是我把手头用过的几种方案拉出来遛一遛,看看谁更靠谱、谁更省心。这篇文章就聊聊我在实际项目中踩过的坑和最终的选择。
方案一:纯前端脱敏(最简单但也最危险)
很多人第一反应是:在 JS 里把数据处理一下,比如手机号变成 138****1234。代码写起来确实快:
function maskPhone(phone) {
if (!phone) return '';
return phone.replace(/(d{3})d{4}(d{4})/, '$1****$2');
}
看起来没问题,但问题大了去了。我之前在一个 SPA 项目里就这么干,结果用户切换账号时,因为 Vue 的 keep-alive 缓存了组件,旧数据没清干净,新用户看到了前一个用户的部分信息。调试半天才发现,脱敏逻辑只在 mounted 里跑了一次,数据变了但视图没更新。
而且,这种方案完全依赖前端逻辑。如果有人直接调接口拿原始数据(比如通过 DevTools 拦截响应),或者 SSR 渲染时没做处理,敏感信息照样裸奔。所以现在我基本不用这种纯前端脱敏——除非是临时 demo,或者数据本身就不算真敏感。
方案二:后端脱敏 + 前端展示(推荐!)
我现在的主力方案是让后端返回脱敏后的数据。比如用户详情接口直接返回 "phone": "138****1234",前端只负责展示。这样从源头上避免了敏感信息泄露的风险。
举个例子,假设我们调用用户信息接口:
// 假设后端已经脱敏
fetch('https://jztheme.com/api/user/profile')
.then(res => res.json())
.then(data => {
// data.phone 已经是 "138****1234"
document.getElementById('phone').innerText = data.phone;
});
这个方案的好处太多了:
- 不管前端怎么缓存、怎么跳转,看到的都是脱敏数据
- SSR、CSR、甚至小程序,都能统一处理
- 安全责任明确:后端控制数据,前端只管展示
当然,也有小麻烦。比如有些页面需要“点击显示完整号码”(像支付宝那样),这时候就得额外开一个带权限校验的接口,专门返回完整信息。但我觉得这反而是好事——强制你做权限控制,而不是图省事把原始数据全扔给前端。
我踩过的一个坑是:后端脱敏规则不统一。有的接口返回 138****1234,有的返回 138******1234,前端还得再处理一遍。后来我们团队定了个规范:所有手机号脱敏格式统一为前三后四,中间四个星号。从此世界清净了。
方案三:前端动态脱敏(灵活但复杂)
有些场景下,后端不能/不愿改,只能靠前端动态脱敏。比如老系统改造,或者第三方 API 返回原始数据。这时候我会用“渲染时脱敏”的方式:
// Vue 组件示例
export default {
computed: {
maskedPhone() {
return this.user.phone?.replace(/(d{3})d{4}(d{4})/, '$1****$2') || '';
}
}
}
或者 React 里:
const MaskedPhone = ({ phone }) => {
const masked = phone?.replace(/(d{3})d{4}(d{4})/, '$1****$2') || '';
return <span>{masked}</span>;
};
这种方式比纯前端脱敏强在哪?它确保每次渲染都重新计算,避免了缓存导致的数据残留。但依然有风险:如果某个地方忘了用这个组件,直接写了 {user.phone},那敏感信息就漏了。
所以我一般会配合 ESLint 规则,禁止直接访问原始字段。比如用自定义 rule 检查是否用了 user.phone 而不是 maskedPhone。不过说实话,这套流程搭起来挺费劲,小项目根本没必要。
谁更灵活?谁更省事?
如果让我选,90% 的情况我会选后端脱敏。虽然初期要推动后端配合,但长期来看维护成本最低,安全边界最清晰。前端不用操心“哪里漏了脱敏”,测试也更容易——只要检查接口返回就行。
纯前端脱敏?除非是内部工具或者数据真的不敏感(比如测试账号),否则我不会再碰。动态脱敏可以作为过渡方案,但一定要有配套的代码规范和检查机制,不然迟早出事。
还有一个细节:脱敏规则要和业务对齐。比如身份证号,有些场景只需要隐藏出生年月(110***********1234),有些要全隐藏(110**************)。后端统一处理的话,改规则只要动一个地方;前端处理就得全局搜索替换,容易遗漏。
我的选型逻辑
总结一下我的决策流程:
- 先问:这个数据真的需要在前端展示吗?能不能只展示图标或状态(比如“已绑定手机”)?能不展示就不展示。
- 如果必须展示,优先推动后端脱敏。哪怕多花两天时间沟通,也值得。
- 实在不行,才用前端动态脱敏,并且必须封装成通用组件,禁止直接使用原始字段。
- 绝对不用“一次性脱敏”(比如只在数据加载时处理一次)。
另外,别忘了 HTTPS!就算脱敏了,如果传输过程被窃听,攻击者还是能拿到原始请求。不过这是另一个话题了……
最后提醒:脱敏不是万能的
即使做了脱敏,也要注意其他泄露点。比如:
- console.log 打印了原始数据(开发时常见,上线前记得删)
- 错误日志上报了用户信息
- 页面 meta 标签或 title 包含敏感内容
我就见过一个项目,手机号脱敏做得很好,但分享链接的 og:image 里包含了用户头像和昵称,结果被爬虫抓走了。所以说,安全是个系统工程,脱敏只是其中一环。
以上是我对敏感信息前端处理的对比总结,核心就一句话:能交给后端的,别自己扛。有不同看法欢迎评论区交流,比如你们是怎么处理“点击显示完整信息”这种需求的?

暂无评论