SpringBoot + 敏感数据脱敏 + AOP:手机号、身份证自动脱敏,满足 GDPR/等保要求

6 阅读5分钟

数据脱敏的重要性

在我们的日常开发工作中,经常会遇到这样的数据安全挑战:

  • 用户的手机号、身份证号、银行卡号等敏感信息需要在日志中记录
  • API接口返回的数据中包含敏感信息,可能被非法获取
  • 系统运维人员能看到完整敏感数据,存在数据泄露风险
  • 法规合规要求,如GDPR、等保等,对敏感数据处理有严格规定

传统的数据脱敏方式往往需要在业务代码中手动处理,不仅工作量大,还容易遗漏。今天我们就来聊聊如何用AOP实现自动化的数据脱敏。 阅读原文

脱敏策略设计

1. 脱敏规则分类

不同类型的敏感数据需要不同的脱敏策略:

  • 手机号:保留前3位和后4位,中间4位用*替换
  • 身份证号:保留前6位和后4位,中间用*替换
  • 银行卡号:保留前4位和后4位,中间用*替换
  • 邮箱:保留用户名前2位和后缀,中间用*替换

2. 脱敏时机选择

  • 输出脱敏:仅在数据输出时脱敏,内部处理保持原样
  • 存储脱敏:数据入库时即进行脱敏
  • 传输脱敏:在网络传输时进行脱敏

AOP实现自动脱敏

1. 脱敏注解定义

@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface SensitiveData {
    SensitiveType type(); // 脱敏类型
    String maskChar() default "*"; // 掩码字符
    int prefixKeep() default 0; // 前缀保留长度
    int suffixKeep() default 0; // 后缀保留长度
}

2. 脱敏类型枚举

public enum SensitiveType {
    PHONE_NUMBER { // 手机号:138****8888
        @Override
        public String mask(String data) {
            if (data == null || data.length() != 11) return data;
            return data.substring(0, 3) + "****" + data.substring(7);
        }
    },
    ID_CARD { // 身份证:110101********1234
        @Override
        public String mask(String data) {
            if (data == null || data.length() < 18) return data;
            return data.substring(0, 6) + "********" + data.substring(14);
        }
    },
    BANK_CARD { // 银行卡:6222 **** **** 8888
        @Override
        public String mask(String data) {
            if (data == null || data.length() < 16) return data;
            return data.substring(0, 4) + " **** **** " + data.substring(data.length() - 4);
        }
    },
    EMAIL { // 邮箱:li**@example.com
        @Override
        public String mask(String data) {
            if (data == null || !data.contains("@")) return data;
            String[] parts = data.split("@");
            if (parts[0].length() <= 2) {
                return parts[0] + "@" + parts[1];
            }
            return parts[0].substring(0, 2) + "**@" + parts[1];
        }
    };

    public abstract String mask(String data);
}

3. 脱敏处理器

@Component
public class SensitiveDataMasker {
    
    public Object maskSensitiveData(Object obj) {
        if (obj == null) return null;
        
        if (obj instanceof String) {
            return maskString((String) obj);
        } else if (obj instanceof Collection) {
            return maskCollection((Collection<?>) obj);
        } else if (obj.getClass().isArray()) {
            return maskArray((Object[]) obj);
        } else {
            return maskObject(obj);
        }
    }
    
    private String maskString(String str) {
        // 如果是JSON格式,需要解析后脱敏
        if (isJson(str)) {
            try {
                Object json = JSON.parse(str);
                Object masked = maskSensitiveData(json);
                return JSON.toJSONString(masked);
            } catch (Exception e) {
                return str; // 解析失败则返回原字符串
            }
        }
        return str;
    }
    
    private Object maskObject(Object obj) {
        Class<?> clazz = obj.getClass();
        
        // 使用反射遍历对象的所有字段
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            try {
                Object fieldValue = field.get(obj);
                
                // 检查字段是否有脱敏注解
                SensitiveData sensitiveAnnotation = field.getAnnotation(SensitiveData.class);
                if (sensitiveAnnotation != null) {
                    Object maskedValue = maskFieldValue(fieldValue, sensitiveAnnotation);
                    field.set(obj, maskedValue);
                } else if (fieldValue != null && !isPrimitiveType(fieldValue.getClass())) {
                    // 递归处理嵌套对象
                    field.set(obj, maskSensitiveData(fieldValue));
                }
            } catch (IllegalAccessException e) {
                // 忽略访问异常
            }
        }
        
        return obj;
    }
    
    private Object maskFieldValue(Object value, SensitiveData annotation) {
        if (value == null) return null;
        
        if (value instanceof String) {
            return annotation.type().mask((String) value);
        } else if (value instanceof Collection) {
            Collection<?> collection = (Collection<?>) value;
            Collection<Object> result = new ArrayList<>();
            for (Object item : collection) {
                result.add(maskSensitiveData(item));
            }
            return result;
        }
        
        return value;
    }
}

4. AOP切面实现

@Aspect
@Component
public class SensitiveDataAspect {
    
    @Autowired
    private SensitiveDataMasker sensitiveDataMasker;
    
    // 拦截Controller层的返回值
    @Around("@within(org.springframework.web.bind.annotation.RestController) || " +
            "@annotation(org.springframework.web.bind.annotation.ResponseBody)")
    public Object maskControllerResponse(ProceedingJoinPoint joinPoint) throws Throwable {
        Object result = joinPoint.proceed();
        return sensitiveDataMasker.maskSensitiveData(result);
    }
    
    // 拦截Service层的日志输出
    @Around("execution(* com.example.service..*(..)) && @annotation(logSensitive)")
    public Object maskServiceReturn(ProceedingJoinPoint joinPoint, LogSensitive logSensitive) throws Throwable {
        Object result = joinPoint.proceed();
        if (logSensitive.mask()) {
            return sensitiveDataMasker.maskSensitiveData(result);
        }
        return result;
    }
    
    // 拦截特定方法的参数和返回值
    @Around("@annotation(com.example.annotation.MaskSensitiveData)")
    public Object maskMethodData(ProceedingJoinPoint joinPoint) throws Throwable {
        // 对方法参数进行脱敏
        Object[] args = joinPoint.getArgs();
        for (int i = 0; i < args.length; i++) {
            args[i] = sensitiveDataMasker.maskSensitiveData(args[i]);
        }
        
        // 执行原方法
        Object result = joinPoint.proceed(args);
        
        // 对返回值进行脱敏
        return sensitiveDataMasker.maskSensitiveData(result);
    }
}

实体类脱敏配置

1. 在实体类中使用注解

@Entity
@Table(name = "user_info")
@Data
public class UserInfo {
    
    @Id
    private Long id;
    
    @SensitiveData(type = SensitiveType.PHONE_NUMBER)
    private String phone;
    
    @SensitiveData(type = SensitiveType.ID_CARD)
    private String idCard;
    
    @SensitiveData(type = SensitiveType.EMAIL)
    private String email;
    
    private String username;
    private String address;
}

2. DTO脱敏配置

@Data
public class UserDTO {
    
    private Long id;
    
    @SensitiveData(type = SensitiveType.PHONE_NUMBER)
    private String phone;
    
    @SensitiveData(type = SensitiveType.ID_CARD)
    private String idCard;
    
    @SensitiveData(type = SensitiveType.BANK_CARD)
    private String bankCard;
    
    // 其他字段...
}

高级脱敏功能

1. 条件脱敏

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ConditionalMask {
    String role() default ""; // 只对特定角色脱敏
    String[] excludeRoles() default {}; // 排除的角色
    boolean adminBypass() default true; // 管理员是否跳过脱敏
}

2. 自定义脱敏规则

@Component
public class CustomMaskRuleProvider {
    
    private final Map<String, Function<String, String>> customRules = new HashMap<>();
    
    @PostConstruct
    public void initCustomRules() {
        // 自定义脱敏规则
        customRules.put("custom-phone", phone -> {
            if (phone == null || phone.length() < 7) return phone;
            return phone.substring(0, 3) + "****" + phone.substring(phone.length() - 4);
        });
        
        customRules.put("custom-idcard", idCard -> {
            if (idCard == null || idCard.length() < 8) return idCard;
            return idCard.substring(0, 6) + "******" + idCard.substring(idCard.length() - 2);
        });
    }
    
    public String applyCustomRule(String ruleName, String data) {
        Function<String, String> rule = customRules.get(ruleName);
        return rule != null ? rule.apply(data) : data;
    }
}

3. 日志脱敏

@Component
public class SensitiveLogMasker {
    
    public String maskLogContent(String logContent) {
        // 使用正则表达式匹配敏感信息并脱敏
        String maskedLog = logContent.replaceAll(
            "(\\d{3})\\d{4}(\\d{4})", "$1****$2" // 手机号脱敏
        );
        
        maskedLog = maskedLog.replaceAll(
            "(\\d{6})\\d{8}(\\d{4})", "$1********$2" // 身份证脱敏
        );
        
        return maskedLog;
    }
}

性能优化策略

1. 缓存脱敏结果

@Component
public class CachedSensitiveDataMasker extends SensitiveDataMasker {
    
    @Autowired
    private CacheManager cacheManager;
    
    @Override
    public Object maskSensitiveData(Object obj) {
        if (obj == null) return null;
        
        // 对于简单类型,使用缓存
        if (obj instanceof String) {
            String original = (String) obj;
            Cache cache = cacheManager.getCache("sensitiveData");
            return cache.get(original, () -> super.maskSensitiveData(original));
        }
        
        return super.maskSensitiveData(obj);
    }
}

2. 批量脱敏优化

public class BatchSensitiveDataProcessor {
    
    public List<Object> batchMaskSensitiveData(List<Object> objects) {
        return objects.parallelStream()
                .map(this::maskSensitiveData)
                .collect(Collectors.toList());
    }
}

最佳实践建议

  1. 分层脱敏:在不同的层级实施不同的脱敏策略
  2. 配置化管理:脱敏规则应支持动态配置
  3. 性能考虑:避免过度脱敏影响系统性能
  4. 安全审计:记录脱敏操作日志便于审计
  5. 测试覆盖:确保脱敏功能的正确性

通过这套完整的敏感数据脱敏方案,我们可以有效保护用户隐私,满足各种法规合规要求。


以上就是本期分享的内容,希望对你有所帮助。更多技术干货,请关注服务端技术精选,我们下期再见!