Java参数脱敏流程规范化指南
总体流程框架
入参校验 → 脱敏规则识别 → 脱敏处理 → 脱敏后输出 → 审计日志
核心流程规范
脱敏规则定义层
// 1.1 定义脱敏规则枚举
public enum DesensitizationType {
PHONE("手机号", "1[3-9]\\d{9}"),
ID_CARD("身份证", "\\d{17}[\\dXx]"),
NAME("姓名", ".{2,4}"),
EMAIL("邮箱", "\\w+@\\w+\\.\\w+"),
BANK_CARD("银行卡", "\\d{16,19}"),
CUSTOM("自定义", "");
private String desc;
private String regex;
// 构造函数、getter方法
}
脱敏注解定义
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface Desensitize {
DesensitizationType type() default DesensitizationType.CUSTOM;
String regex() default "";
String replacement() default "";
int startInclude() default 0; // 保留前几位明文
int endInclude() default 0; // 保留后几位明文
char placeholder() default '*'; // 占位符
}
脱敏处理器接口
public interface Desensitizer {
String desensitize(String value, Desensitize annotation);
boolean support(DesensitizationType type);
}
标准脱敏实现
手机号脱敏
@Component
public class PhoneDesensitizer implements Desensitizer {
@Override
public String desensitize(String value, Desensitize annotation) {
if (StringUtils.isBlank(value)) return value;
// 默认规则:保留前3后4,中间****
int start = annotation.startInclude() > 0 ? annotation.startInclude() : 3;
int end = annotation.endInclude() > 0 ? annotation.endInclude() : 4;
return value.replaceAll("(\\d{" + start + "})\\d+(\\d{" + end + "})",
"$1" + repeat('*', value.length() - start - end) + "$2");
}
@Override
public boolean support(DesensitizationType type) {
return type == DesensitizationType.PHONE;
}
}
身份证脱敏
@Component
public class IdCardDesensitizer implements Desensitizer {
@Override
public String desensitize(String value, Desensitize annotation) {
if (StringUtils.isBlank(value)) return value;
// 默认规则:保留前6后4,中间******
return value.replaceAll("(\\d{6})\\d+(\\d{4})", "$1******$2");
}
@Override
public boolean support(DesensitizationType type) {
return type == DesensitizationType.ID_CARD;
}
}
注解驱动配置
实体类配置
public class UserVO {
@Desensitize(type = DesensitizationType.NAME, startInclude = 1, endInclude = 0)
private String name;
@Desensitize(type = DesensitizationType.PHONE)
private String phone;
@Desensitize(type = DesensitizationType.ID_CARD)
private String idCard;
@Desensitize(type = DesensitizationType.EMAIL, regex = "(\\w)(\\w+)(@\\w+\\.\\w+)", replacement = "$1***$3")
private String email;
@Desensitize(type = DesensitizationType.BANK_CARD, startInclude = 4, endInclude = 4)
private String bankCard;
// getter/setter
}
方法参数配置
@RestController
public class UserController {
@PostMapping("/user")
public Result<UserVO> createUser(@RequestBody @Desensitize UserVO user) {
// 自动脱敏处理
return Result.success(user);
}
@GetMapping("/user/{phone}")
public Result<UserVO> getUserByPhone(@PathVariable @Desensitize(type=PHONE) String phone) {
// 脱敏后的手机号查询
return Result.success(userService.getByPhone(phone));
}
}
脱敏执行流程实现
AOP切面实现
@Aspect
@Component
public class DesensitizationAspect {
@Autowired
private List<Desensitizer> desensitizers;
@Around("@annotation(desensitize) || @within(desensitize)")
public Object handleDesensitization(ProceedingJoinPoint pjp, Desensitize desensitize) throws Throwable {
// 处理参数
Object[] args = pjp.getArgs();
for (int i = 0; i < args.length; i++) {
if (args[i] != null) {
args[i] = processDesensitization(args[i]);
}
}
// 执行方法
Object result = pjp.proceed(args);
// 处理返回值
return processDesensitization(result);
}
private Object processDesensitization(Object obj) {
if (obj == null) return null;
// 处理字符串
if (obj instanceof String) {
return desensitizeString((String) obj);
}
// 处理集合
if (obj instanceof Collection) {
((Collection<?>) obj).forEach(this::processDesensitization);
return obj;
}
// 处理对象
Field[] fields = obj.getClass().getDeclaredFields();
for (Field field : fields) {
Desensitize annotation = field.getAnnotation(Desensitize.class);
if (annotation != null) {
field.setAccessible(true);
try {
Object value = field.get(obj);
if (value instanceof String) {
field.set(obj, desensitizeByAnnotation((String) value, annotation));
}
} catch (IllegalAccessException e) {
log.error("Desensitization failed for field: {}", field.getName(), e);
}
}
}
return obj;
}
}
序列化拦截器实现
@JsonComponent
public class DesensitizationSerializer extends JsonSerializer<String> {
@Override
public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
// 获取当前序列化上下文中的脱敏配置
Desensitize annotation = getAnnotationFromContext();
if (annotation != null && StringUtils.isNotBlank(value)) {
gen.writeString(desensitizeByAnnotation(value, annotation));
} else {
gen.writeString(value);
}
}
private String desensitizeByAnnotation(String value, Desensitize annotation) {
// 查找支持的脱敏器
Desensitizer desensitizer = findDesensitizer(annotation.type());
if (desensitizer != null) {
return desensitizer.desensitize(value, annotation);
}
return value;
}
}
配置化管理
application.yml配置
desensitization:
enabled: true
default-placeholder: '*'
rules:
phone:
enabled: true
start-include: 3
end-include: 4
id-card:
enabled: true
start-include: 6
end-include: 4
include-params: true
include-response: true
exclude-paths:
- /health
- /metrics
配置类
@Configuration
@ConfigurationProperties(prefix = "desensitization")
public class DesensitizationConfig {
private boolean enabled = true;
private char defaultPlaceholder = '*';
private Map<String, Rule> rules = new HashMap<>();
private boolean includeParams = true;
private boolean includeResponse = true;
private List<String> excludePaths = new ArrayList<>();
@Data
public static class Rule {
private boolean enabled = true;
private int startInclude;
private int endInclude;
private String regex;
private String replacement;
}
// getter/setter
}
审计日志规范
@Component
public class DesensitizationLogger {
@Autowired
private AuditLogService auditLogService;
@EventListener
public void handleDesensitizationEvent(DesensitizationEvent event) {
AuditLog log = AuditLog.builder()
.timestamp(LocalDateTime.now())
.operator(event.getOperator())
.operationType("DESENSITIZATION")
.sourceInfo(event.getSourceInfo())
.sensitiveType(event.getSensitiveType())
.originalValue(event.getOriginalValue()) // 仅在授权环境下记录
.desensitizedValue(event.getDesensitizedValue())
.result(event.getResult())
.build();
auditLogService.save(log);
}
}
测试规范
单元测试
@SpringBootTest
class DesensitizationTest {
@Autowired
private DesensitizationService desensitizationService;
@Test
void testPhoneDesensitization() {
String phone = "13812345678";
String result = desensitizationService.desensitizePhone(phone);
assertEquals("138****5678", result);
}
@Test
void testCustomRule() {
UserVO user = new UserVO();
user.setName("张三");
user.setPhone("13812345678");
UserVO desensitized = desensitizationService.desensitizeUser(user);
assertEquals("张*", desensitized.getName());
assertEquals("138****5678", desensitized.getPhone());
}
}
异常处理规范
@ControllerAdvice
public class DesensitizationExceptionHandler {
@ExceptionHandler(DesensitizationException.class)
public ResponseEntity<ErrorResponse> handleDesensitizationException(DesensitizationException e) {
ErrorResponse response = ErrorResponse.builder()
.code("DESENSITIZATION_ERROR")
.message("数据脱敏处理异常")
.detail(e.getMessage())
.timestamp(LocalDateTime.now())
.build();
// 记录错误日志,不泄露原始数据
log.error("Desensitization failed: type={}, field={}",
e.getType(), e.getField());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(response);
}
}
最佳实践建议
- 分层处理:在Service层进行脱敏,Controller层负责参数校验
- 缓存策略:对频繁使用的脱敏规则进行缓存
- 动态配置:支持运行时动态修改脱敏规则
- 性能优化:使用正则表达式预编译,避免重复编译
- 安全考虑:脱敏后的数据不可逆,原始数据严格保护
- 版本兼容:API版本变化时,脱敏规则同步更新
- 监控告警:脱敏异常时及时告警,避免敏感数据泄露
通过以上规范化流程,可以有效保障Java应用中敏感数据的脱敏处理安全性和一致性。
