如何通过注解实现接口返回数据脱敏?

左手的ㄟ右手 2024-03-26 14:48 140阅读 0赞

思路

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

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

代码

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

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

    1. DataMaskingFunc maskFunc() default DataMaskingFunc.NO_MASK;

    }

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

    public interface DataMaskingOperation {

    1. String MASK_CHAR = "*";
    2. String mask(String content, String maskChar);

    }

  1. public enum DataMaskingFunc {
  2. /**
  3. * 脱敏转换器
  4. */
  5. NO_MASK((str, maskChar) -> {
  6. return str;
  7. }),
  8. ALL_MASK((str, maskChar) -> {
  9. if (StringUtils.hasLength(str)) {
  10. StringBuilder sb = new StringBuilder();
  11. for (int i = 0; i < str.length(); i++) {
  12. sb.append(StringUtils.hasLength(maskChar) ? maskChar : DataMaskingOperation.MASK_CHAR);
  13. }
  14. return sb.toString();
  15. } else {
  16. return str;
  17. }
  18. });
  19. private final DataMaskingOperation operation;
  20. private DataMaskingFunc(DataMaskingOperation operation) {
  21. this.operation = operation;
  22. }
  23. public DataMaskingOperation operation() {
  24. return this.operation;
  25. }
  26. }
  27. public final class DataMaskingSerializer extends StdScalarSerializer<Object> {
  28. private final DataMaskingOperation operation;
  29. public DataMaskingSerializer() {
  30. super(String.class, false);
  31. this.operation = null;
  32. }
  33. public DataMaskingSerializer(DataMaskingOperation operation) {
  34. super(String.class, false);
  35. this.operation = operation;
  36. }
  37. public boolean isEmpty(SerializerProvider prov, Object value) {
  38. String str = (String)value;
  39. return str.isEmpty();
  40. }
  41. public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException {
  42. if (Objects.isNull(operation)) {
  43. String content = DataMaskingFunc.ALL_MASK.operation().mask((String) value, null);
  44. gen.writeString(content);
  45. } else {
  46. String content = operation.mask((String) value, null);
  47. gen.writeString(content);
  48. }
  49. }
  50. public final void serializeWithType(Object value, JsonGenerator gen, SerializerProvider provider, TypeSerializer typeSer) throws IOException {
  51. this.serialize(value, gen, provider);
  52. }
  53. public JsonNode getSchema(SerializerProvider provider, Type typeHint) {
  54. return this.createSchemaNode("string", true);
  55. }
  56. public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint) throws JsonMappingException {
  57. this.visitStringFormat(visitor, typeHint);
  58. }
  59. }
  1. 自定义AnnotationIntrospector,适配我们自定义注解返回相应的Serializer

    @Slf4j
    public class DataMaskingAnnotationIntrospector extends NopAnnotationIntrospector {

    1. @Override
    2. public Object findSerializer(Annotated am) {
    3. DataMasking annotation = am.getAnnotation(DataMasking.class);
    4. if (annotation != null) {
    5. return new DataMaskingSerializer(annotation.maskFunc().operation());
    6. }
    7. return null;
    8. }

    }

  2. 覆盖ObjectMapper

    @Configuration(

    1. proxyBeanMethods = false

    )
    public class DataMaskConfiguration {

    1. @Configuration(
    2. proxyBeanMethods = false
    3. )
    4. @ConditionalOnClass({Jackson2ObjectMapperBuilder.class})
    5. static class JacksonObjectMapperConfiguration {
    6. JacksonObjectMapperConfiguration() {
    7. }
    8. @Bean
    9. @Primary
    10. ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
    11. ObjectMapper objectMapper = builder.createXmlMapper(false).build();
    12. AnnotationIntrospector ai = objectMapper.getSerializationConfig().getAnnotationIntrospector();
    13. AnnotationIntrospector newAi = AnnotationIntrospectorPair.pair(ai, new DataMaskingAnnotationIntrospector());
    14. objectMapper.setAnnotationIntrospector(newAi);
    15. return objectMapper;
    16. }
    17. }

    }

  3. 返回对象加上注解

    public class User implements Serializable {

    1. /**
    2. * 主键ID
    3. */
    4. private Long id;
    5. /**
    6. * 姓名
    7. */
    8. @DataMasking(maskFunc = DataMaskingFunc.ALL_MASK)
    9. private String name;
    10. /**
    11. * 年龄
    12. */
    13. private Integer age;
    14. /**
    15. * 邮箱
    16. */
    17. @DataMasking(maskFunc = DataMaskingFunc.ALL_MASK)
    18. private String email;

    }

发表评论

表情:
评论列表 (有 0 条评论,140人围观)

还没有评论,来说两句吧...

相关阅读

    相关 SpringBoot实现返回数据脱敏

    介绍 SpringBoot实现返回数据脱敏 有时,敏感数据返回时,需要进行隐藏处理,但是如果一个字段一个字段的进行硬编码处理的话,不仅增加了工作量,而且后期需