Skip to content

Spring 中的数据脱敏与敏感信息保护

很多项目一提到“安全”,很容易先想到加密。但真实企业开发里,还有另一条非常常见、也非常容易被忽视的主线:数据脱敏。

这篇文章重点讲:

  1. 数据脱敏到底是什么
  2. 它和加密、哈希有什么区别
  3. 为什么脱敏在日志、接口返回、后台页面里特别重要
  4. Spring 项目里常见的脱敏落地方式有哪些
  5. 工程上最容易踩的坑是什么

1. 数据脱敏到底是什么

数据脱敏 可以看成:在不暴露完整敏感信息的前提下,只展示或输出必要的一部分内容。

例如:

  1. 手机号 13812345678 显示成 138****5678
  2. 身份证号只显示前 6 位和后 4 位
  3. 银行卡号只显示最后 4 位
  4. 姓名显示成 张*

它解决的核心问题是:很多场景并不需要看到完整明文,但如果系统直接把明文吐出去,泄漏风险会非常高。


2. 脱敏和加密、哈希有什么区别

这几个词经常混在一起,但它们解决的问题不同。

2.1 脱敏

重点是控制展示内容,减少泄漏面。

2.2 加密

重点是让别人看不懂原始内容。

2.3 哈希

重点是把原始内容映射成不可逆摘要。

可以直接这样区分:

  1. 脱敏是“少给别人看”
  2. 加密是“就算拿到了也看不懂”
  3. 哈希是“通常不打算再还原回原文”

要注意的是:脱敏不替代加密,加密也不替代脱敏。

很多场景里,这两件事反而要同时存在。


3. 为什么数据脱敏在企业项目里特别重要

很多敏感数据泄漏,不一定是数据库被拖走那么严重的事故。

更常见的问题其实来自:

  1. 后台页面把完整手机号直接展示给了不该看到的人
  2. 接口响应把完整身份证号返回到了前端
  3. 日志打印把 token、手机号、密码字段打出来了
  4. 测试环境、工单系统、排障截图里带出了真实敏感信息

所以数据脱敏解决的不是“黑客攻防”这一层,而是系统内部日常开发、运维、测试、客服、分析等链路里的过度暴露问题。


4. 哪些数据通常应该优先考虑脱敏

企业开发里,下面这些字段通常都很值得优先纳入脱敏范围:

  1. 手机号
  2. 身份证号
  3. 银行卡号
  4. 邮箱
  5. 姓名
  6. 地址
  7. token
  8. access key、secret key
  9. 密码及支付口令

这里要特别注意:密码这类字段更常见的正确做法不是“脱敏显示”,而是根本不应该以明文方式出现。


5. Spring 项目里最常见的脱敏场景

如果按工程位置来拆,最常见的通常有 4 类:

  1. 页面展示脱敏
  2. 接口返回脱敏
  3. 日志输出脱敏
  4. 导出文件脱敏

5.1 页面展示脱敏

例如后台管理系统里,客服查看用户资料时,很多时候并不需要看到完整身份证号或银行卡号。

这时更合理的做法通常是:默认只展示脱敏值,只有极少数高权限操作才允许看原文。

5.2 接口返回脱敏

有些接口给前端返回用户信息时,如果把完整手机号、证件号直接透出,前端日志、浏览器缓存、抓包链路都可能扩大暴露面。

5.3 日志输出脱敏

这是最容易被忽视的一类。

很多线上敏感信息泄漏,不是因为数据库被攻破,而是因为日志打印太“诚实”了。

5.4 导出文件脱敏

报表导出、Excel 导出、对账单下载这些场景,也经常需要脱敏。

因为文件一旦被转发,传播范围往往比系统内页面更难控制。


6. 一个最简单的脱敏工具示例

看最容易理解的一层:工具方法脱敏。

例如手机号脱敏:

java
public final class MaskingUtils {

    private MaskingUtils() {
    }

    public static String maskPhone(String phone) {
        if (phone == null || phone.length() < 7) {
            return phone;
        }
        return phone.substring(0, 3) + "****" + phone.substring(phone.length() - 4);
    }

    public static String maskEmail(String email) {
        if (email == null || !email.contains("@")) {
            return email;
        }
        int index = email.indexOf('@');
        if (index <= 1) {
            return "***" + email.substring(index);
        }
        return email.substring(0, 1) + "***" + email.substring(index);
    }
}

在返回对象组装阶段使用:

java
public class UserVO {

    private Long id;
    private String phone;
    private String email;

    public static UserVO from(User user) {
        UserVO vo = new UserVO();
        vo.id = user.getId();
        vo.phone = MaskingUtils.maskPhone(user.getPhone());
        vo.email = MaskingUtils.maskEmail(user.getEmail());
        return vo;
    }
}

这个例子虽然简单,但已经表达了一个很重要的工程习惯:脱敏最好发生在“对外输出”这一层,而不是等前端自己决定怎么遮。


7. 如果想做得更统一,Spring 里怎么落地

企业项目里,如果每个接口都手写一次脱敏,后面通常会越来越乱。

更稳妥的思路通常有几种:

  1. 在 DTO / VO 转换层统一处理
  2. 用 Jackson 自定义序列化器处理
  3. 用 AOP 或响应包装层做统一脱敏
  4. 日志框架层做敏感字段过滤

7.1 在 DTO / VO 层统一处理

这是最直接、也最容易维护的一种方式。

它的好处是:

  1. 脱敏边界清楚
  2. 业务语义明确
  3. 不容易误伤内部处理逻辑

缺点是:如果接口很多、字段很多,容易写重复代码。

7.2 用 Jackson 自定义序列化器

如果希望对象在转 JSON 时自动脱敏,可以在 Spring MVC 的响应序列化阶段处理。

例如自定义一个手机号脱敏序列化器:

java
public class PhoneMaskSerializer extends JsonSerializer<String> {

    @Override
    public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        gen.writeString(MaskingUtils.maskPhone(value));
    }
}

然后在字段上使用:

java
public class UserVO {

    private Long id;

    @JsonSerialize(using = PhoneMaskSerializer.class)
    private String phone;
}

这类方案的优势是 脱敏逻辑和输出过程绑定得更紧

但也要注意边界 不是所有场景都适合一序列化就统一脱敏,否则有时会影响内部接口或管理端真实需求

7.3 日志框架层脱敏

对日志来说,更常见的做法不是业务代码里到处手写:log.info("phone={}", MaskingUtils.maskPhone(phone));

而是:

  1. 在日志切面里统一处理
  2. 在日志框架里做正则替换
  3. 在请求日志拦截器里屏蔽指定字段

因为日志问题的本质是:不该把敏感字段明文打出去。

如果每个人都靠自觉,很难长期稳定。


8. 一个自定义脱敏注解示意

如果想把脱敏表达得更统一,也可以做一个简单的自定义注解。

例如:

java
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MaskPhone {
}

然后再配合统一序列化逻辑或切面处理。

这里要注意的是:注解本身只负责表达“这个字段需要脱敏”,真正让它生效的,仍然是后续处理逻辑。

这和 Spring 里其他很多自定义注解的主线是一致的。


9. 数据脱敏不等于权限控制

这是非常容易被忽略的边界。

有些团队会觉得:字段已经脱敏了,就算安全控制做完了。

其实不是。

更具体一点:

  1. 脱敏解决的是“展示时少暴露”
  2. 权限控制解决的是“谁可以访问、谁不可以访问”
  3. 审计解决的是“谁看过、谁导出过、谁操作过”

所以真正稳妥的做法通常是:脱敏 + 权限控制 + 审计

而不是只做其中一层。


10. 工程上最容易踩的坑

10.1 误区一:数据库加密了,就不需要脱敏

数据库加密解决的是存储层保护;一旦数据被正常查询出来,输出链路依然可能泄漏。

10.2 误区二:脱敏放前端做就行

如果后端已经把完整敏感信息返回给前端,暴露面其实已经扩大了。

10.3 误区三:日志是内部看的,所以可以打全量明文

真实事故里,日志往往就是最容易扩散敏感数据的地方。

10.4 误区四:所有场景都用同一种脱敏规则

不同场景对展示粒度要求不同:

  1. 用户自己看自己的资料
  2. 客服看用户资料
  3. 运营看统计报表
  4. 风控看高风险事件

这些场景的脱敏强度通常不应该完全一样。


11. 一份更实用的落地建议

如果你在 Spring 项目里真的要做敏感信息保护,下面这套习惯通常更稳:

  1. 对手机号、身份证号、银行卡号、邮箱等字段建立统一脱敏规则
  2. 默认在对外响应层做脱敏,而不是把责任丢给前端
  3. 日志打印默认屏蔽密码、token、证件号、手机号等字段
  4. 对导出、报表、截图、审计链路额外关注脱敏策略
  5. 高敏数据同时配合加密、权限控制和访问审计

12. 总结

把数据脱敏压缩成最核心的几句话,就是:

  1. 脱敏解决的是“该展示多少”,不是“数据能不能看懂”
  2. 脱敏不等于加密,也不等于权限控制
  3. Spring 项目里最常见的脱敏位置,是响应层、日志层和导出层
  4. 真正稳妥的敏感信息保护,通常要把脱敏、加密、权限和审计一起考虑

基于 VitePress 构建的个人技术笔记。