项目数据脱敏实现

使用自定义注解, 结合jackson的序列化, 实现自定义的数据脱敏
文章拆分为几个部分:
1.代码实现
2.源码分析
3.定义抽象层, 剥离业务代码


代码实现

1. 定义注解

@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(
    using = MaskSerializer.class
)
public @interface MaskField {
    MaskEnum value();
}

其中比较重要的是@JsonSerialize, 来看一下这个注解的作用: 指定字段的序列化类
翻看jackson的源码, 看这一下这个注解是如何生效的

BeanSerializerFactory.class

protected BeanPropertyWriter _constructWriter(SerializerProvider prov,
            BeanPropertyDefinition propDef,
            PropertyBuilder pb, boolean staticTyping, AnnotatedMember accessor)
        throws JsonMappingException
    {
        final PropertyName name = propDef.getFullName();
        JavaType type = accessor.getType();
        BeanProperty.Std property = new BeanProperty.Std(name, type, propDef.getWrapperName(),
                accessor, propDef.getMetadata());
          //注意看这里, accessor是使用了@MaskField注解的字段的get方法, 这个方法进去看一下
        JsonSerializer<?> annotatedSerializer = findSerializerFromAnnotation(prov,
                accessor);
        if (annotatedSerializer instanceof ResolvableSerializer) {
            ((ResolvableSerializer) annotatedSerializer).resolve(prov);
        }

        annotatedSerializer = prov.handlePrimaryContextualization(annotatedSerializer, property);
        TypeSerializer contentTypeSer = null;
        if (type.isContainerType() || type.isReferenceType()) {
            contentTypeSer = findPropertyContentTypeSerializer(type, prov.getConfig(), accessor);
        }
        TypeSerializer typeSer = findPropertyTypeSerializer(type, prov.getConfig(), accessor);
        return pb.buildWriter(prov, propDef, type, annotatedSerializer,
                        typeSer, contentTypeSer, accessor, staticTyping);
    }

protected JsonSerializer<Object> findSerializerFromAnnotation(SerializerProvider prov,
            Annotated a)
        throws JsonMappingException
    {
    //这里取得了序列化类, 进入方法内看一下
        Object serDef = prov.getAnnotationIntrospector().findSerializer(a);
        if (serDef == null) {
            return null;
        }
        JsonSerializer<Object> ser = prov.serializerInstance(a, serDef);
        // One more thing however: may need to also apply a converter:
        return (JsonSerializer<Object>) findConvertingSerializer(prov, a, ser);
    }

JacksonAnnotationIntrospector.class

public Object findSerializer(Annotated a)
    {
    //根据字段的注解, 获取注解中标注的JsonSerialize实现类
        JsonSerialize ann = _findAnnotation(a, JsonSerialize.class);
        if (ann != null) {
            @SuppressWarnings("rawtypes")
            Class<? extends JsonSerializer> serClass = ann.using();
            if (serClass != JsonSerializer.None.class) {
                return serClass;
            }
        }
        
        JsonRawValue annRaw =  _findAnnotation(a, JsonRawValue.class);
        if ((annRaw != null) && annRaw.value()) {
            // let's construct instance with nominal type:
            Class<?> cls = a.getRawType();
            return new RawSerializer<Object>(cls);
        }       
        return null;
    }

至此, 我们可以看到, 新的注解MaskField是如何将字段使用自定义的序列化类了.

2. 定义枚举

接下来, 将我们需要的字段使用新的@MaskField注解, 先定义几个类型

public enum MaskEnum {
   
   
    /**
     * 中文名
     */
    NAME,
    /**
     * 身份证号
     */
    ID_CARD,
    /**
     * 手机号
     */
    MOBILE,
    /**
     * 地址
     */
    ADDRESS,
    /**
     * 电子邮件
     */
    EMAIL,
    /**
     * 银行卡
     */
    BANK_CARD,

    /**
     * 自定义字段
     */
    CUSTOM_FIELD
}

使用这个字段
User.class

@MaskField(MaskEnum.MOBILE)
private String mobile;

3. 定制序列化类

好了, 已经将User对象的mobile字段与MaskSerialize类绑定, 接下来设计一下MaskSerialize类, 看如何针对手机字段进行脱敏

首先看一下空实现
public class MaskSerializer<T> extends JsonSerializer<T> implements ContextualSerializer{

	//自定义序列化方法, 其中T就是字段值
    @Override
    public void serialize(T t, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {}

	//构造序列化类
    @Override
    public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {}   
}

先看一下序列化类是如何构造的, 关注一下ContextualSerializer的createContextual方法
BeanSeralizerFactory.class

protected BeanPropertyWriter _constructWriter(SerializerProvider prov,
            BeanPropertyDefinition propDef,
            PropertyBuilder pb, boolean staticTyping, AnnotatedMember accessor)
        throws JsonMappingException
    {
        final PropertyName name = propDef.getFullName();
        JavaType type = accessor.getType();
        //这里的property是具体的要序列化的字段
        BeanProperty.Std property = new BeanProperty.Std(name, type, propDef.getWrapperName(),
                accessor, propDef.getMetadata());

		//注解是否指定了JsonSerializer, 指定了MaskSerializer, 所以这里会返回MaskSerializer
        JsonSerializer<?> annotatedSerializer = findSerializerFromAnnotation(prov,
                accessor);

        if (annotatedSerializer instanceof ResolvableSerializer) {
            ((ResolvableSerializer) annotatedSerializer).resolve(prov);
        }

		//应用JsonSerializer中的createContextual方法
        annotatedSerializer = prov.handlePrimaryContextualization(annotatedSerializer, property);
       
        TypeSerializer contentTypeSer = null;
      
        if (type.isContainerType() || type.isReferenceType()) {
            contentTypeSer = findPropertyContentTypeSerializer(type, prov.getConfig(), accessor);
        }
  
        TypeSerializer typeSer = findPropertyTypeSerializer(type, prov.getConfig(), accessor);
        return pb.buildWriter(prov, propDef, type, annotatedSerializer,
                        typeSer, contentTypeSer, accessor, staticTyping);
    }

SerializerProvider.class

    public JsonSerializer<?> handlePrimaryContextualization(JsonSerializer<?> ser,
            BeanProperty property)
        throws JsonMappingException
    {
        if (ser != null) {
            if (ser instanceof ContextualSerializer) {
            //应用createContextual的方法
                ser = ((ContextualSerializer) ser).createContextual(this, property);
            }
        }
        return ser;
    }

根据上面的代码, 我们可以看到在字段的序列化器构造的时候, 会调用ContextualSerializer接口的实现方法, 每个字段只会调用一次.
到此, 我们简单的分析一下用到了哪些关键类

  1. BeanSerializerFactory
    • BeanSerializerFactory 是 Jackson 库中的一个工厂类,用于创建 BeanSerializer(Java 对象到 JSON 的序列化器)。
    • 主要作用是根据 Java 类的属性和注解等信息,生成用于将 Java 对象序列化为 JSON 格式的序列化器。
    • 它负责处理 Java 对象的序列化过程,将 Java 对象转换为 JSON 格式的数据。
  2. DefaultSerializerProvider
    • DefaultSerializerProvider 是 Jackson 库中的默认序列化器提供者,用于管理和提供序列化器。
    • 主要作用是根据给定的 Java 类型和配置信息,获取适当的序列化器(如 JsonSerializer)来执行对象到 JSON 的转换。
    • 它负责选择合适的序列化器,并在序列化过程中对序列化器进行初始化和配置。
  3. JsonSerializer
    • JsonSerializer 是 Jackson 库中的抽象类,用于定义自定义的序列化器。
    • 主要作用是根据不同的 Java 类型和需求,编写自定义的序列化逻辑,以便将特定类型的 Java 对象序列化为 JSON 格式。
      -可以通过扩展 JsonSerializer 类,并实现 serialize 方法来定义如何将特定类型的对象转换为 JSON 格式。

BeanSerializerFactory 用于创建 BeanSerializer 进行 Java 对象到 JSON 的序列化,DefaultSerializerProvider 用于管理和提供序列化器,而 JsonSerializer 则是用于编写自定义的序列化逻辑的抽象类。这些类一起协作,实现了 Java 对象到 JSON 数据的转换功能。


源码分析-加载过程

1. 字段的Serializer是如何加载的

当序列化到对象时, 根据BeanDescription将每个字段都找到合适的Serializer, 这里的beanDesc是整个dto对象
在这里插入图片描述将每个字段都把寻找到的每个字段的Serializer放入BeanSerializerBuilder中, 后续可以直接引用
在这里插入图片描述
在下面这一步可以看到, dto对象的序列化类已经加载完, 其中的每个字段的序列化类也已加载好
在这里插入图片描述
如下图, 加载完对象的序列化类以后, 放入缓存中, 后续可以直接使用
在这里插入图片描述
点开序列化缓存对象, 看看里面已经加载了哪些东西
在这里插入图片描述
到此加载已经全部接受, 接下来, 看一下对象的序列化是怎么进行的

找到之前的序列化类, 调用序列化方法
在这里插入图片描述
每个字段逐个进行序列化
在这里插入图片描述
进入到具体的字段的序列化, 这里看一下使用了MaskField注解的手机号字段
先反射取得字段值, 再调用字段的序列化的serialize方法
在这里插入图片描述
在这里插入图片描述
经过上面的步骤, 我们已经跟完整个对象的序列化, 总结一下就是利用BeanSerializerFactory, 将每个对象都提前设置好序列化类, 并放入缓存中, 当需要将对象真正的进行序列化时, 在取出已经构造好的序列化类, 对对象的每个字段进行逐个序列化


剥离业务代码, 实现项目自定义

在具体的项目实践中, 实际上可能不只有一个序列化实现类, 在不同的项目中可能有各自的脱敏规则, 如何写出一段可定制实现的序列化呢?
我们需要在构造MaskField注解的时候指定序列化类, 这时候要在哪里插入自定义的序列化类?

带着以上两个问题, 可以明确的是在定义MaskField注解时必须指定一个序列化类, 而且这个序列化类必须具有一定给的灵活性, 即它的serializer方法要可以改变.
可以向MaskSerializer类注入一个抽象对象, 这个抽象对象允许其他的项目继承并重写, 在项目中注入, 这样就可以在每个项目的MaskSerializer类中植入自定义的序列化方法了!
看看方法的实现:

1.定义一个接口

public interface DynamicMaskSerializer<T> {
    String serialize(T t);
    void setType(MaskEnum type);


}

2. 定义一个默认的实现类

这里实现了一个空的实现类, 即不做任何的操作, 直接序列化字段.
同时这里使用了@ConditionalOnMissingBean, 当项目中不存在DynamicMaskSerializer时才会使用默认的实现类

@Configuration
public class DefalutMaskSerializerConfig {

    @Bean
    @ConditionalOnMissingBean(DynamicMaskSerializer.class)
    public DynamicMaskSerializer defalutMaskSerializer() {
        return new DefaultMaskSerializer();
    }

    public class DefaultMaskSerializer<T> implements DynamicMaskSerializer<T> {

        MaskEnum type;
        
        public void setType(MaskEnum type) {
            this.type = type;
        }

        @Override
        public String serialize(T t) {

            return String.valueOf(t);

        }
    }
}

3. 修改MaskSerializer

 public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {

        // 为空直接跳过
        if (beanProperty == null) {
            return serializerProvider.findNullValueSerializer(beanProperty);
        }
        // 非String类直接跳过
        if (Objects.equals(beanProperty.getType().getRawClass(), String.class) || Objects.equals(beanProperty.getType().getRawClass(), Map.class)) {

			//获取到注解上的MaskField
            MaskField maskField = beanProperty.getAnnotation(MaskField.class);
            if (maskField == null) {
                maskField = beanProperty.getContextAnnotation(MaskField.class);
            }
            if (maskField != null) {

                // 如果能得到注解,就将注解的 value 传入 MaskSerialize
                MaskSerializer maskSerializer = new MaskSerializer(maskField.value());
                // 从容器中获取DynamicMaskSerializer 
                DynamicMaskSerializer dynamicMaskSerializer = (DynamicMaskSerializer) SpringContextUtil.getBean(DynamicMaskSerializer.class);
                //DynamicMaskSerializer构造的时候需要明确是什么类型
                dynamicMaskSerializer.setType(maskField.value());
                maskSerializer.dynamicMaskSerializer = dynamicMaskSerializer;
                return maskSerializer;
            }
        }
        return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);
    }
 

4. 结合上下文Context, 控制脱敏开关

    public void serialize(T t, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
       MaskSetting maskSetting = MaskSerializerContext.getMaskSetting();
       Boolean enable = maskSetting.getEnable();
       if (enable) {
           String result  = dynamicMaskSerializer.serialize(t);
           jsonGenerator.writeString(result);
       } else {
           jsonGenerator.writeObject(t);
       }

   }

至此, 整个框架的脱敏功能就大致实现, 感谢阅读

  • 22
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值