本文提供一个技术干货,只需要在需要脱敏的字段上加一个注解,轻松实现报文脱敏。学会之后,你在项目组的江湖地位将直线上升!
直接上干货。
定义枚举类
public enum DesensitizationStrategy {
PHONE(str -> str.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2")),
EMAIL(str -> str.replaceAll("(\\w?)(\\w+)(\\w)(@\\w+\\.[a-z]+(\\.[a-z]+)?)", "$1****$3$4")),
ID_CARD(str -> str.replaceAll("(\\d{4})\\d{10}(\\w{4})", "$1****$2")),
BANK_CARD(str -> str.replaceAll("(\\d{4})\\d{8}(\\d{4})", "$1****$2"));
private final Function<String, String> function;
private DesensitizationStrategy(Function<String, String> function) {
this.function = function;
}
public String apply(String str) {
return function.apply(str);
}
public static void main(String[] args) {
System.out.println(DesensitizationStrategy.PHONE.apply("12345678901"));
}
}
仅仅只需要这个枚举类就可以当做一个脱敏工具类来用,可以关注上面的main方法“DesensitizationStrategy.PHONE.apply(“12345678901”)”,这就是实现了手机号脱敏。
这个枚举类的特别之处就是使用了函数式接口作为枚举的属性,学会这招能将很多工具类改为枚举类的形式,相当优雅。
这个枚举类只是开胃菜,优雅的是下面这个注解的写法。
定义注解
/**
* 脱敏注解
* 用于标记需要脱敏的字段
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = DesensitizationSerializer.class)
public @interface Desensitization {
/**
* 敏感数据类型。
* @return 敏感数据类型枚举类
*/
DesensitizationStrategy value() default DesensitizationStrategy.PHONE;
}
这个自定义注解的特别之处就是@JacksonAnnotationsInside,@JacksonAnnotationsInside是一个组合注解,主要标记在用户的自定义注解上,那么这个用户自定义注解上标记的所有其他注解也会生效。也就是说,我们的自定义注解上标记的注解–@JsonSerialize(using = DesensitizationSerializer.class)会和这个自定义注解一起生效。
用过jackson的都知道,@JsonSerialize(using = DesensitizationSerializer.class)这个注解的意思就是将被注解的字段的json序列化交给类“DesensitizationSerializer”实现,下面我们新建这个类:
序列化注解实现类
public class DesensitizationSerializer extends JsonSerializer<String> implements ContextualSerializer {
private Desensitization desensitization;
@Override
public void serialize(String s, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeString(desensitization.value().apply(s));
}
@Override
public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {
if (beanProperty != null) {
// 说明有Desensitization注解,下面就是判断注解的类型,然后返回对应的序列化器
// 这里只处理String类型的字段
if (Objects.equals(beanProperty.getType().getRawClass(), String.class)) {
// 获取字段上的注解
Desensitization desensitization = beanProperty.getAnnotation(Desensitization.class);
// 如果字段上没有注解,获取getter方法上的注解
if (desensitization == null) {
// 获取getter方法上的注解
desensitization = beanProperty.getContextAnnotation(Desensitization.class);
}
if (desensitization != null) {
this.desensitization = desensitization;
return this;
}
}
return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);
}
return serializerProvider.findNullValueSerializer(null);
}
}
这个类分别实现了ContextualSerializer 接口和继承了JsonSerializer父类,其实一看就懂,这里简单解释一下。
JsonSerializer类就是jackson提供的字段序列化方法的超类,我们可以实现它自定义序列化被注解的字段,比如上面提到的注解@JsonSerialize(using = DesensitizationSerializer.class),意思就是指定类DesensitizationSerializer为该字段的序列化方法类,被指定的这个类必须继承JsonSerializer超类。
ContextualSerializer接口是对JsonSerializer超类的加强,配合它一起使用。ContextualSerializer 的createContextual方法提供改字段的类型和注解,并能够根据特定的场景选择适合的序列化器,就像上面代码中所写的。并且,createContextual方法只会在第一次序列化字段时调用(因为字段的上下文信息在运行期不会改变),所以不用担心影响性能。
以上就是全部干货,使用方法如下:
使用方法
仅仅需要在你的响应题对象中,将需要脱敏的字段加上注解就好了。比如,我有个接口响应体是这个People类,将该类需要脱敏的字段加上Desensitization注解,并标记字段的脱敏类型。
@Data
public class People {
private Integer id;
private String name;
private Integer sex;
private Integer age;
@Desensitization(value = DesensitizationStrategy.PHONE) // 隐藏手机号
private String phone;
@Desensitization(value = DesensitizationStrategy.EMAIL) // 隐藏邮箱
private String email;
@Desensitization(value = DesensitizationStrategy.ID_CARD) // 隐藏身份证号
private String iDCard;
private String sign;
}
写个Controller测试一下:
@GetMapping("/get-people")
public People getPeopleInfo() {
People people = new People();
people.setId(1);
people.setName("张三");
people.setSex(1);
people.setAge(18);
people.setPhone("18888888888");
people.setEmail("147641545@Gmail.com");
people.setIDCard("1234567891234567");
people.setSign("Hello World");
return people;
}
运行结果
{
"id": 1,
"name": "张三",
"sex": 1,
"age": 18,
"phone": "188****8888",
"email": "1****5@Gmail.com",
"iDCard": "1234****6789",
"sign": "Hello World"
}