数据脱敏的重要性
在我们的日常开发工作中,经常会遇到这样的数据安全挑战:
- 用户的手机号、身份证号、银行卡号等敏感信息需要在日志中记录
- 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());
}
}
最佳实践建议
- 分层脱敏:在不同的层级实施不同的脱敏策略
- 配置化管理:脱敏规则应支持动态配置
- 性能考虑:避免过度脱敏影响系统性能
- 安全审计:记录脱敏操作日志便于审计
- 测试覆盖:确保脱敏功能的正确性
通过这套完整的敏感数据脱敏方案,我们可以有效保护用户隐私,满足各种法规合规要求。
以上就是本期分享的内容,希望对你有所帮助。更多技术干货,请关注服务端技术精选,我们下期再见!