自定义注解实现数据脱敏

说明

以下使用的脱敏方式是使用jackson的JsonSerializer实现的,有些情况可能不支持,不支持可以用三方提供的脱敏工具方法,也可以自己封装脱敏工具方法

自定义注解

import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.haier.boot.enums.DataMaskingType;
import com.haier.boot.serializer.DataMaskSerializer;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * 数据脱敏自定义注解,此注解为复合注解,功能是根据jackson的Serializer(对象序列化)实现。
 * 使用时需要注意
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = DataMaskSerializer.class)
public @interface DataMasking {

    /**
     * 数据脱敏类型
     */
    DataMaskingType type() default DataMaskingType.CUSTOM;
    /**
     * 脱敏前部分留字符数
     */
    int prefix() default -1;
    /**
     * 脱敏后部分留字符数
     */
    int suffix() default -1;
}

自定义枚举

/**
 * 可以通过新增枚举来处理特定的处理方式,例如USER_ID,prefix意思是字符串前部保留2个字符或者汉字,
 * suffix意思是字符串后部保留1个字符或者汉字,其余的替换成 *
 * 也可以使用CUSTOM 枚举值个性化指定前后保留的字符或者汉字(前后需要同时指定才生效),指定方式为DataMasking的prefix和suffix
 * 除了CUSTOM可以自定义prefix和suffix,其余的只能是使用已经定义好的prefix和suffix
 */
public enum DataMaskingType{

    /**
     * 用户ID
     */
    USER_ID(2, 1),

    /**
     * 自定义类型(需要通过DataMasking的prefix和suffix指定,两个需要同时指定,否则不生效)
     */
    CUSTOM(-1, -1);
    //数据脱敏前面留几个字符
    private final int prefix;
    //数据脱敏后面留几个字符
    private final int suffix;

    DataMaskingType(int prefix, int suffix) {
        this.prefix = prefix;
        this.suffix = suffix;
    }

    public int getPrefix() {
        return prefix;
    }

    public int getSuffix() {
        return suffix;
    }
}

自定义序列化类

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import com.haier.boot.annotation.DataMasking;
import com.haier.boot.enums.DataMaskingType;

import java.io.IOException;
import java.util.Objects;

/**
 * 数据脱敏
 */
public final class DataMaskSerializer extends JsonSerializer<String> implements ContextualSerializer {
    private DataMaskingType type;
    private Integer prefix;
    private Integer suffix;

    public DataMaskSerializer() {
    }

    public DataMaskSerializer(DataMaskingType type, Integer prefix, Integer suffix) {
        this.type = type;
        this.prefix = prefix;
        this.suffix = suffix;
    }

    @Override
    public void serialize(String value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        // 此处是将值进行转化脱敏逻辑,可以使用hutool工具去处理,也可以自行编写脱敏逻辑
        if (Objects.equals(type, DataMaskingType.CUSTOM)) {
            jsonGenerator.writeString(this.hide(value, prefix, suffix));
        }else {
            jsonGenerator.writeString(this.hide(value, type.getPrefix(), type.getSuffix()));
        }
    }

    //前prefix后suffix脱敏方法(必要时可将此方法放置到工具类中)
    public String hide(String value ,Integer prefix, Integer suffix) {
        if (prefix < 0 || suffix < 0) {
            return value;
        }
        if (value == null || "".equals(value) || value.length() <= (prefix + suffix)) return value;
        // 截取前prefix位
        String front = value.substring(0, prefix);
        // 截取后suffix位
        String back = value.substring(value.length() - suffix);
        // 生成中间的星号
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < value.length() - (prefix + suffix); i++) {
            sb.append("*");
        }
        // 拼接结果
        return front + sb.toString() + back;
    }

    @Override
    public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {
        if (Objects.nonNull(beanProperty)) {
            //判断是否为string类型
            if (Objects.equals(beanProperty.getType().getRawClass(), String.class)) {
                DataMasking anno = beanProperty.getAnnotation(DataMasking.class);
                if (Objects.isNull(anno)) {
                    anno = beanProperty.getContextAnnotation(DataMasking.class);
                }
                if (Objects.nonNull(anno)) {
                    return new DataMaskSerializer(anno.type(), anno.prefix(), anno.suffix());
                }
            }
            return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);
        }
        return serializerProvider.findNullValueSerializer(null);
    }

    public DataMaskingType getType() {
        return type;
    }

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

    public Integer getPrefix() {
        return prefix;
    }

    public void setPrefix(Integer prefix) {
        this.prefix = prefix;
    }

    public Integer getSuffix() {
        return suffix;
    }

    public void setSuffix(Integer suffix) {
        this.suffix = suffix;
    }
}

使用

// 数据脱敏,按照此处指定的prefix和suffix脱敏
@DataMasking(type = DataMaskingType.CUSTOM, prefix = 0, suffix = 1)
private String billType;

// 指定的prefix和suffix不生效,但是数据脱敏,
// 按照DataMaskingType中指定的prefix和suffix脱敏
@DataMasking(type = DataMaskingType.USER_ID, prefix = 3, suffix = 4)
private String billCode;

// 数据不脱敏
@DataMasking(type = DataMaskingType.CUSTOM)
private String pickType;

// 数据脱敏,按照DataMaskingType中指定的prefix和suffix脱敏
@DataMasking(type = DataMaskingType.USER_ID)
private String inLocatorCode;

参考

参考文章

posted @ 2023-09-18 14:25  品书读茶  阅读(104)  评论(0编辑  收藏  举报