自定义注解实现数据脱敏
说明
以下使用的脱敏方式是使用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;
参考
纸上得来终觉浅,绝知此事要躬行。