⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 juejin.cn/post/7110110794188062727 「Mr_Mating」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注**微信公众号:【芋道源码】**有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

下午惬意时光,突然产品小姐姐走到我面前,打断我短暂的摸鱼time,企图与我进行深入交流,还好我早有防备没有闪,打开瑞star的点单页面,暗示没有一杯coffee解决不了的需求,需求是某些接口返回的信息,涉及到敏感数据的必须进行脱敏操作,我思考一反,表示某问题,马上安排。

思路

1.要做成可配置多策略的脱敏操作,要不然一个个接口进行脱敏操作,重复的工作量太多,很显然违背了“多写一行算我输”的程序员规范,思来想去,定义数据脱敏注解和数据脱敏逻辑的接口, 在返回类上,对需要进行脱敏的属性加上,并指定对应的脱敏策略操作。

2.接下来我只需要拦截控制器返回的数据,找到带有脱敏注解的属性操作即可,一开始打算用@ControllerAdvice去实现,但发现需要自己去反射类获取注解,当返回对象比较复杂,需要递归去反射,性能一下子就会降低,于是换种思路,我想到平时使用的@JsonFormat,跟我现在的场景很类似,通过自定义注解跟字段解析器,对字段进行自定义解析,tql

代码

1. 自定义数据注解,并可以配置数据脱敏策略

@Target({ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataMasking {

DataMaskingFunc maskFunc() default DataMaskingFunc.NO_MASK;

}

2. 自定义Serializer,参考jackson的StringSerializer,下面的示例只针对String类型进行脱敏

public interface DataMaskingOperation {

String MASK_CHAR = "*";

String mask(String content, String maskChar);

}

public enum DataMaskingFunc {

/**
* 脱敏转换器
*/
NO_MASK((str, maskChar) -> {
return str;
}),
ALL_MASK((str, maskChar) -> {
if (StringUtils.hasLength(str)) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < str.length(); i++) {
sb.append(StringUtils.hasLength(maskChar) ? maskChar : DataMaskingOperation.MASK_CHAR);
}
return sb.toString();
} else {
return str;
}
});

private final DataMaskingOperation operation;

private DataMaskingFunc(DataMaskingOperation operation) {
this.operation = operation;
}

public DataMaskingOperation operation() {
return this.operation;
}

}

public final class DataMaskingSerializer extends StdScalarSerializer<Object> {
private final DataMaskingOperation operation;

public DataMaskingSerializer() {
super(String.class, false);
this.operation = null;
}

public DataMaskingSerializer(DataMaskingOperation operation) {
super(String.class, false);
this.operation = operation;
}


public boolean isEmpty(SerializerProvider prov, Object value) {
String str = (String)value;
return str.isEmpty();
}

public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException {
if (Objects.isNull(operation)) {
String content = DataMaskingFunc.ALL_MASK.operation().mask((String) value, null);
gen.writeString(content);
} else {
String content = operation.mask((String) value, null);
gen.writeString(content);
}
}

public final void serializeWithType(Object value, JsonGenerator gen, SerializerProvider provider, TypeSerializer typeSer) throws IOException {
this.serialize(value, gen, provider);
}

public JsonNode getSchema(SerializerProvider provider, Type typeHint) {
return this.createSchemaNode("string", true);
}

public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint) throws JsonMappingException {
this.visitStringFormat(visitor, typeHint);
}
}

3. 自定义AnnotationIntrospector,适配我们自定义注解返回相应的Serializer

@Slf4j
public class DataMaskingAnnotationIntrospector extends NopAnnotationIntrospector {

@Override
public Object findSerializer(Annotated am) {
DataMasking annotation = am.getAnnotation(DataMasking.class);
if (annotation != null) {
return new DataMaskingSerializer(annotation.maskFunc().operation());
}
return null;
}

}

4. 覆盖ObjectMapper

@Configuration(
proxyBeanMethods = false
)
public class DataMaskConfiguration {

@Configuration(
proxyBeanMethods = false
)
@ConditionalOnClass({Jackson2ObjectMapperBuilder.class})
static class JacksonObjectMapperConfiguration {
JacksonObjectMapperConfiguration() {
}

@Bean
@Primary
ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
ObjectMapper objectMapper = builder.createXmlMapper(false).build();
AnnotationIntrospector ai = objectMapper.getSerializationConfig().getAnnotationIntrospector();
AnnotationIntrospector newAi = AnnotationIntrospectorPair.pair(ai, new DataMaskingAnnotationIntrospector());
objectMapper.setAnnotationIntrospector(newAi);
return objectMapper;
}
}

}

5. 返回对象加上注解

public class User implements Serializable {
/**
* 主键ID
*/
private Long id;

/**
* 姓名
*/
@DataMasking(maskFunc = DataMaskingFunc.ALL_MASK)
private String name;

/**
* 年龄
*/
private Integer age;

/**
* 邮箱
*/
@DataMasking(maskFunc = DataMaskingFunc.ALL_MASK)
private String email;

}

文章目录
  1. 1. 思路
  2. 2. 代码
    1. 2.1. 1. 自定义数据注解,并可以配置数据脱敏策略
    2. 2.2. 2. 自定义Serializer,参考jackson的StringSerializer,下面的示例只针对String类型进行脱敏
    3. 2.3. 3. 自定义AnnotationIntrospector,适配我们自定义注解返回相应的Serializer
    4. 2.4. 4. 覆盖ObjectMapper
    5. 2.5. 5. 返回对象加上注解