一、需求场景:为什么需要脱敏框架?
在数据安全合规要求下,敏感信息处理成为系统必备能力。典型场景:
- 用户隐私保护(手机号、身份证、邮箱等)
- 日志敏感信息过滤
- 接口返回数据自动脱敏
传统方案痛点:
- 硬编码脱敏逻辑,维护成本高
- 不同字段需重复编写相似代码
- 无法动态调整脱敏规则
二、框架设计全景图
三、核心实现三步走
1. 注解体系设计(声明式配置)
顶级脱敏注解
@Documented
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside // 此注解是其他所有 jackson 注解的元注解,打上了此注解的注解表明是 jackson 注解的一部分
@JsonSerialize(using = StringDesensitizeSerializer.class) // 指定序列化器
public @interface DesensitizeBy {
/**
* 脱敏处理器
*/
@SuppressWarnings("rawtypes")
Class<? extends DesensitizationHandler> handler();
}
邮箱脱敏注解
@Documented
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@DesensitizeBy(handler = EmailDesensitizationHandler.class)
public @interface EmailDesensitize {
/**
* 匹配的正则表达式
*/
String regex() default "(^.)[^@]*(@.*$)";
/**
* 替换规则,邮箱;
*
* 比如:example@gmail.com 脱敏之后为 e****@gmail.com
*/
String replacer() default "$1****$2";
/**
* 是否禁用脱敏
*
* 支持 Spring EL 表达式,如果返回 true 则跳过脱敏
*/
String disable() default "";
}
2. 处理机制实现(策略模式+模板方法模式)
脱敏处理器
public interface DesensitizationHandler<T extends Annotation> {
/**
* 脱敏
*
* @param origin 原始字符串
* @param annotation 注解信息
* @return 脱敏后的字符串
*/
String desensitize(String origin, T annotation);
/**
* 是否禁用脱敏的 Spring EL 表达式
*
* 如果返回 true 则跳过脱敏
*
* @param annotation 注解信息
* @return 是否禁用脱敏的 Spring EL 表达式
*/
default String getDisable(T annotation) {
// 约定:默认就是 enable() 属性。如果不符合,子类重写
try {
return (String) ReflectUtil.invoke(annotation, "disable");
} catch (Exception ex) {
return "";
}
}
}
正则表达式脱敏处理器抽象类(模板方法模式+策略模式)
public abstract class AbstractRegexDesensitizationHandler<T extends Annotation>
implements DesensitizationHandler<T> {
//==========================================不可变部分即公共方法即模板方法模式的实现===========================
@Override
public String desensitize(String origin, T annotation) {
// 1. 判断是否禁用脱敏
Object disable = SpringExpressionUtils.parseExpression(getDisable(annotation));
if (Boolean.TRUE.equals(disable)) {
return origin;
}
// 2. 执行脱敏
String regex = getRegex(annotation);
String replacer = getReplacer(annotation);
return origin.replaceAll(regex, replacer);
}
//==========================================可变部分抽象由具体子类实现===========================
/**
* 获取注解上的 regex 参数
*
* @param annotation 注解信息
* @return 正则表达式
*/
abstract String getRegex(T annotation);
/**
* 获取注解上的 replacer 参数
*
* @param annotation 注解信息
* @return 待替换的字符串
*/
abstract String getReplacer(T annotation);
}
EmailDesensitize的脱敏处理器
//==========================================邮箱脱敏策略===========================
public class EmailDesensitizationHandler extends AbstractRegexDesensitizationHandler<EmailDesensitize> {
@Override
String getRegex(EmailDesensitize annotation) {
return annotation.regex();
}
@Override
String getReplacer(EmailDesensitize annotation) {
return annotation.replacer();
}
}
3. Jackson序列化集成
脱敏序列化器
public class StringDesensitizeSerializer extends StdSerializer<String> implements ContextualSerializer {
@Getter
@Setter
private DesensitizationHandler desensitizationHandler;
protected StringDesensitizeSerializer() {
super(String.class);
}
@Override
public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) {
DesensitizeBy annotation = beanProperty.getAnnotation(DesensitizeBy.class);
if (annotation == null) {
return this;
}
// 创建一个 StringDesensitizeSerializer 对象,使用 DesensitizeBy 对应的处理器
StringDesensitizeSerializer serializer = new StringDesensitizeSerializer();
serializer.setDesensitizationHandler(Singleton.get(annotation.handler()));
return serializer;
}
@Override
@SuppressWarnings("unchecked")
public void serialize(String value, JsonGenerator gen, SerializerProvider serializerProvider) throws IOException {
if (StrUtil.isBlank(value)) {
gen.writeNull();
return;
}
// 获取序列化字段
Field field = getField(gen);
// 自定义处理器
DesensitizeBy[] annotations = AnnotationUtil.getCombinationAnnotations(field, DesensitizeBy.class);
if (ArrayUtil.isEmpty(annotations)) {
gen.writeString(value);
return;
}
for (Annotation annotation : field.getAnnotations()) {
if (AnnotationUtil.hasAnnotation(annotation.annotationType(), DesensitizeBy.class)) {
value = this.desensitizationHandler.desensitize(value, annotation);
gen.writeString(value);
return;
}
}
gen.writeString(value);
}
/**
* 获取字段
*
* @param generator JsonGenerator
* @return 字段
*/
private Field getField(JsonGenerator generator) {
String currentName = generator.getOutputContext().getCurrentName();
Object currentValue = generator.getCurrentValue();
Class<?> currentValueClass = currentValue.getClass();
return ReflectUtil.getField(currentValueClass, currentName);
}
}
四、使用示例:三步完成脱敏
1. 添加注解
public class UserVO {
@EmailDesensitize(replacer = "$1***$2") // 自定义替换规则
private String email;
@PhoneDesensitize // 使用默认手机号脱敏规则
private String phone;
}
2. 返回结果对比
// 原始数据
{
"email": "test@example.com",
"phone": "13812345678"
}
// 脱敏后
{
"email": "t***@example.com",
"phone": "138****5678"
}
3. 动态关闭脱敏(SpEL支持)
@EmailDesensitize(disable = "${app.desensitize.disable:false}")
private String email;
五、设计亮点分析
1. 开闭原则实践
- 新增类型:只需添加注解+处理器
- 修改规则:调整注解参数即可
2. 性能优化点
- 单例模式:处理器全局单例复用
- 缓存机制:注解解析结果缓存
- 懒加载:按需初始化处理器
3. 灵活配置能力
// 多规则叠加示例
@EmailDesensitize(regex = "(?<=.{2}).", replacer = "*")
@ConditionalOnProperty("security.enabled")
private String email;