SpringBoot + 数据脱敏策略 + 注解驱动:手机号、银行卡号返回时自动掩码

前言

在当今数据安全日益重要的时代,保护用户敏感信息已成为每个系统必须面对的挑战。特别是在接口返回数据时,如何在不影响业务逻辑的情况下,对敏感信息进行脱敏处理,是一个值得深入研究的问题。

本文将详细介绍如何使用 Spring Boot 实现基于注解驱动的数据脱敏策略,实现手机号、银行卡号等敏感信息在返回时自动掩码处理。

一、数据脱敏的重要性

1. 法规要求

  • 《网络安全法》:要求网络运营者对用户个人信息进行保护
  • 《个人信息保护法》:明确规定个人敏感信息的处理规则
  • 《数据安全法》:要求建立数据分类分级保护制度

2. 业务需求

  • 保护用户隐私:防止用户敏感信息泄露
  • 符合审计要求:满足内部审计和外部监管需求
  • 提升系统安全性:减少敏感信息在系统中的暴露面
  • 增强用户信任:让用户对系统的数据处理更有信心

3. 常见脱敏场景

场景敏感信息脱敏要求
用户注册手机号、邮箱部分掩码
订单管理银行卡号、身份证号部分掩码
员工管理薪资、联系方式部分掩码
日志记录用户信息、交易数据全量脱敏

二、技术选型

技术版本用途
Spring Boot3.2.0基础框架
Jackson2.15.0JSON 序列化/反序列化
Spring AOP-切面编程
自定义注解-标记需要脱敏的字段
反射-运行时处理字段

三、实现方案

1. 方案对比

方案优点缺点适用场景
注解 + 序列化无侵入性、灵活配置只处理返回数据接口返回脱敏
AOP 切面可处理多种场景性能开销较大全链路脱敏
数据库层面存储和查询都脱敏无法恢复原始数据存储脱敏
工具类简单直接代码重复、维护困难局部脱敏

2. 核心实现原理

本文采用 注解 + Jackson 序列化 的方案,具体实现原理如下:

  1. 自定义脱敏注解:标记需要脱敏的字段
  2. 脱敏序列化器:实现 Jackson 的 JsonSerializer 接口
  3. 脱敏策略:定义不同类型的脱敏规则
  4. 注册序列化器:将序列化器注册到 Jackson

四、代码实现

1. 项目结构

SpringBoot-Data-Masking-Demo/
├── src/
│ └── main/
│ ├── java/
│ │ └── com/
│ │ └── example/
│ │ └── masking/
│ │ ├── DataMaskingApplication.java
│ │ ├── annotation/
│ │ │ └── DataMasking.java
│ │ ├── serializer/
│ │ │ └── DataMaskingSerializer.java
│ │ ├── strategy/
│ │ │ ├── DataMaskingStrategy.java
│ │ │ ├── MaskingType.java
│ │ │ └── impl/
│ │ │ ├── PhoneMaskingStrategy.java
│ │ │ ├── BankCardMaskingStrategy.java
│ │ │ ├── IdCardMaskingStrategy.java
│ │ │ └── CustomMaskingStrategy.java
│ │ ├── config/
│ │ │ └── JacksonConfig.java
│ │ ├── controller/
│ │ │ └── UserController.java
│ │ └── entity/
│ │ └── User.java
│ └── resources/
│ └── application.yml
├── pom.xml
└── README.md

### 2. 核心代码实现

#### 2.1 自定义脱敏注解

```java
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = DataMaskingSerializer.class)
public @interface DataMasking {
    /**
     * 脱敏类型
     */
    MaskingType type();
    
    /**
     * 自定义脱敏规则(当 type 为 CUSTOM 时使用)
     */
    String rule() default "";
    
    /**
     * 前缀保留长度
     */
    int prefix() default 3;
    
    /**
     * 后缀保留长度
     */
    int suffix() default 4;
}

enum MaskingType {
    PHONE,         // 手机号脱敏
    BANK_CARD,     // 银行卡号脱敏
    ID_CARD,       // 身份证号脱敏
    CUSTOM,        // 自定义脱敏
    EMAIL,         // 邮箱脱敏
    ADDRESS        // 地址脱敏
}

2.2 脱敏序列化器

public class DataMaskingSerializer extends JsonSerializer<String> implements ContextualSerializer {
    
    private DataMasking dataMasking;
    
    @Override
    public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        if (value == null) {
            gen.writeNull();
            return;
        }
        
        String maskedValue = maskValue(value, dataMasking);
        gen.writeString(maskedValue);
    }
    
    @Override
    public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
        DataMasking dataMasking = property.getAnnotation(DataMasking.class);
        if (dataMasking == null) {
            dataMasking = property.getContextAnnotation(DataMasking.class);
        }
        
        DataMaskingSerializer serializer = new DataMaskingSerializer();
        serializer.dataMasking = dataMasking;
        return serializer;
    }
    
    private String maskValue(String value, DataMasking dataMasking) {
        MaskingType type = dataMasking.type();
        DataMaskingStrategy strategy = getStrategy(type);
        return strategy.mask(value, dataMasking);
    }
    
    private DataMaskingStrategy getStrategy(MaskingType type) {
        // 根据类型获取对应策略
    }
}

2.3 脱敏策略接口

public interface DataMaskingStrategy {
    /**
     * 脱敏处理
     * @param value 原始值
     * @param dataMasking 脱敏注解
     * @return 脱敏后的值
     */
    String mask(String value, DataMasking dataMasking);
}

2.4 具体脱敏策略实现

@Component
public class PhoneMaskingStrategy implements DataMaskingStrategy {
    @Override
    public String mask(String value, DataMasking dataMasking) {
        if (value == null || value.length() != 11) {
            return value;
        }
        return value.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2");
    }
}

@Component
public class BankCardMaskingStrategy implements DataMaskingStrategy {
    @Override
    public String mask(String value, DataMasking dataMasking) {
        if (value == null || value.length() < 8) {
            return value;
        }
        int prefix = dataMasking.prefix();
        int suffix = dataMasking.suffix();
        if (prefix + suffix >= value.length()) {
            return value;
        }
        StringBuilder sb = new StringBuilder();
        sb.append(value.substring(0, prefix));
        sb.append("****".repeat((value.length() - prefix - suffix) / 4 + 1));
        sb.append(value.substring(value.length() - suffix));
        return sb.toString();
    }
}

3. 实体类使用示例

@Data
public class User {
    private Long id;
    private String name;
    
    @DataMasking(type = MaskingType.PHONE)
    private String phone;
    
    @DataMasking(type = MaskingType.EMAIL)
    private String email;
    
    @DataMasking(type = MaskingType.ID_CARD)
    private String idCard;
    
    @DataMasking(type = MaskingType.BANK_CARD)
    private String bankCard;
    
    @DataMasking(type = MaskingType.ADDRESS, prefix = 2, suffix = 2)
    private String address;
}

4. 控制器示例

@RestController
@RequestMapping("/api/users")
public class UserController {
    
    @GetMapping("/{id}")
    public ResponseEntity<User> getUser(@PathVariable Long id) {
        User user = new User();
        user.setId(id);
        user.setName("张三");
        user.setPhone("13812345678");
        user.setEmail("zhangsan@example.com");
        user.setIdCard("110101199001011234");
        user.setBankCard("6222021234567890123");
        user.setAddress("北京市朝阳区建国路88号");
        return ResponseEntity.ok(user);
    }
}

五、测试结果

原始数据

User user = new User();
user.setId(1L);
user.setName("张三");
user.setPhone("13812345678");
user.setEmail("zhangsan@example.com");
user.setIdCard("110101199001011234");
user.setBankCard("6222021234567890123");
user.setAddress("北京市朝阳区建国路88号");

脱敏后返回结果

{
  "id": 1,
  "name": "张三",
  "phone": "138****5678",
  "email": "zha****@example.com",
  "idCard": "110***********1234",
  "bankCard": "6222****7890123",
  "address": "北京**********88号"
}

六、高级功能

1. 动态脱敏规则

  • 基于环境:不同环境使用不同脱敏规则
  • 基于用户角色:不同角色看到不同程度的脱敏
  • 基于配置:通过配置中心动态调整脱敏规则

2. 自定义脱敏策略

@DataMasking(
    type = MaskingType.CUSTOM,
    rule = "3,4",  // 前缀3位,后缀4位
    prefix = 3,
    suffix = 4
)
private String customField;

3. 嵌套对象脱敏

@Data
public class Order {
    private Long id;
    
    @DataMasking(type = MaskingType.BANK_CARD)
    private String bankCard;
    
    private User user;  // User 中的脱敏注解会自动生效
}

4. 集合类型脱敏

@Data
public class UserList {
    private List<@DataMasking(type = MaskingType.PHONE) String> phones;
    private List<User> users;  // 列表中的对象会自动脱敏
}

七、性能优化

1. 缓存优化

  • 策略缓存:缓存脱敏策略实例
  • 结果缓存:缓存脱敏结果,避免重复计算
  • 注解缓存:缓存字段的脱敏配置

2. 序列化优化

  • 懒加载:只在需要时进行脱敏
  • 批量处理:批量处理脱敏操作
  • 并行处理:利用多线程处理大集合

3. 内存优化

  • 字符串处理:使用 StringBuilder 避免字符串拼接
  • 对象池:复用脱敏策略对象
  • GC 优化:减少临时对象创建

八、最佳实践

1. 脱敏策略选择

数据类型推荐脱敏策略示例
手机号保留前3后4138****5678
身份证号保留前3后4110***********1234
银行卡号保留前4后36222****123
邮箱保留前2后域名zh****@example.com
地址保留省市和门牌号北京市朝阳区********88号
姓名保留姓张**

2. 安全最佳实践

  • 最小权限原则:只对必要的字段进行脱敏
  • 一致性:相同类型的数据脱敏规则保持一致
  • 可审计性:记录脱敏操作日志
  • 定期评估:定期评估脱敏策略的有效性

3. 开发最佳实践

  • 统一配置:集中管理脱敏规则
  • 文档化:详细记录脱敏策略和实现
  • 测试覆盖:编写脱敏功能的单元测试
  • 代码审查:确保脱敏实现的正确性

九、常见问题

Q1: 如何处理 null 值?

A: 在序列化器中添加 null 检查,对 null 值直接返回,不进行脱敏处理。

Q2: 如何处理不同长度的数据?

A: 在脱敏策略中添加长度检查,对不符合长度要求的数据不进行脱敏。

Q3: 如何实现自定义脱敏规则?

A: 使用 MaskingType.CUSTOM 类型,通过 rule 属性指定自定义规则。

Q4: 如何处理嵌套对象?

A: Jackson 会自动递归处理嵌套对象中的注解,无需特殊处理。

Q5: 如何提高脱敏性能?

A: 可以:

  1. 缓存脱敏结果
  2. 使用更高效的字符串处理方法
  3. 避免在高频接口中过度使用脱敏

Q6: 如何实现基于角色的脱敏?

A: 可以:

  1. 在脱敏策略中注入用户角色信息
  2. 根据角色选择不同的脱敏规则
  3. 使用 AOP 切面实现更复杂的逻辑

十、总结

通过 Spring Boot + 注解驱动的数据脱敏方案,我们可以:

  • 无侵入性:通过注解标记需要脱敏的字段,不影响业务逻辑
  • 灵活性:支持多种脱敏策略和自定义规则
  • 高性能:利用 Jackson 序列化机制,性能开销小
  • 可扩展性:易于添加新的脱敏策略和规则
  • 安全性:有效保护用户敏感信息

这种方案不仅满足了法规要求,也提升了系统的安全性和用户信任度。在实际项目中,我们可以根据具体业务需求,选择合适的脱敏策略,为用户数据提供全方位的保护。

更多技术文章,欢迎关注公众号服务端技术精选**,及时获取最新动态。**


标题:SpringBoot + 数据脱敏策略 + 注解驱动:手机号、银行卡号返回时自动掩码
作者:jiangyi
地址:http://jiangyi.space/articles/2026/03/16/1773481267367.html
公众号:服务端技术精选
    评论
    0 评论
avatar

取消