一、背景
像身份证、手机号、住址等敏感信息在日志打印、前端用户展示时需要对数据脱敏处理。
二、实现方案
- 在数据库查询阶段处理 可以在mybatis拦截器中对结果映射时进行脱敏处理
- 数据序列化阶段处理 基于Jackson序列化器对数据动态处理
三、实现代码
以下是基于Jackson的脱敏方案
- 常用的脱敏类型枚举
@Getter
@AllArgsConstructor
public enum MaskType {
PHONE(3, 4),
//...
NONE(0, 0),
;
public final int prefixLength;
public final int suffixLength;
}
- 枚举注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = MaskSerializer.class)
public @interface Mask {
/**
* 前缀长度,优先级L2
*/
int prefixLength() default 3;
/**
* 后缀长度,优先级L2
*/
int suffixLength() default 4;
/**
* 脱敏替换字符,优先级L1
*/
String maskChar() default "*";
/**
* 脱敏数据类型,优先级L1,L2优先级配置失效
*/
MaskType maskType() default MaskType.NONE;
}
- 序列化器
@NoArgsConstructor
@AllArgsConstructor
public class MaskSerializer extends JsonSerializer<Object> implements ContextualSerializer {
private int prefixLength = 3;
private int suffixLength = 4;
private String maskChar = "*";
public final static String template = "(?<=\\w{%s})\\w(?=\\w{%s})";
@Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
if (Objects.nonNull(value)) {
if (value instanceof String[]) {
String[] arr = (String[]) value;
gen.writeStartArray();
for (String s : arr) {
gen.writeString(s.trim().replaceAll(String.format(template, prefixLength, suffixLength), maskChar));
}
gen.writeEndArray();
}
if (value instanceof List) {
List<?> list = (List<?>) value;
if (!CollectionUtils.isEmpty(list) && list.get(0).getClass().equals(String.class)) {
gen.writeStartArray();
for (Object o : list) {
gen.writeString(((String) o).trim().replaceAll(String.format(template, prefixLength, suffixLength), maskChar));
}
gen.writeEndArray();
}
}
if (value instanceof String) {
gen.writeString(((String) value).trim().replaceAll(String.format(template, prefixLength, suffixLength), maskChar));
}
}
gen.writeNull();
}
@Override
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
if (Objects.nonNull(property)) {
Mask mask = property.getAnnotation(Mask.class);
if (Objects.isNull(mask)) {
mask = property.getContextAnnotation(Mask.class);
}
//可以改造成初始化regex正则表达式
if (Objects.nonNull(mask)) {
if (!mask.maskType().equals(MaskType.NONE)) {
return new MaskSerializer(mask.maskType().prefixLength,
mask.maskType().suffixLength,
mask.maskChar());
}
return new MaskSerializer(mask.prefixLength(),
mask.suffixLength(),
mask.maskChar());
}
return prov.findValueSerializer(property.getType(), property);
} else {
return prov.findNullValueSerializer(null);
}
}
@Override
public Class<Object> handledType() {
return Object.class;
}
}