背景
前两天看到有人写了个jackson的数据脱敏,突然挺感兴趣的,感觉以后的开发过程中也能够用到,就照着抄了一下,顺便自己消化了一下。
目的
在服务端返回数据时,利用Jackson序列化完成数据脱敏,达到对敏感信息脱敏展示。
降低重复开发量,提升开发效率
形成统一有效的脱敏规则
可基于重写默认脱敏实现的desensitize方法,实现可扩展、可自定义的个性化业务场景的脱敏需求
参考链接
前期准备
StdSerializer:所有标准序列化程序所使用的基类,这个是编写自定义序列化程序所推荐使用的基类,这边用到它的serialize方法,来做序列化转化。
ContextualSerializer:是Jackson 提供的另一个序列化相关的接口,它的作用是通过字段已知的上下文信息定制JsonSerializer,调用的createContextual方法,简单来讲就是通过它注入新增的序列化对象。
实现思路
1.我们首先定义一个脱敏器接口****Desensitization
2.给脱敏接口一个默认的接口实现DefaultDesensitization,不定义方法,他也是一个接口,不做实现。
3.定义一个手机号脱敏实现类MobileNoDesensitization,实现DefaultDesensitization,实现脱敏方法desensitize,做自己的脱敏处理
4.定义一个脱敏注解Desensitize,定义需要的属性,脱敏实现类desensitization
5.定义默认的脱敏注解DefaultDesensitize,在这个注解上增加脱敏注解Desensitize,并指定脱敏实现类,这里是DefaultDesensitization
6.定义手机号脱敏注解MobileNoDesensitize,在这个注解上增加脱敏注解Desensitize,并指定脱敏实现类,这里是MobileNoDesensitization
7.定义脱敏序列化器,createContextual方法通过它注入新增的序列化对象。serialize方法,来做序列化转化
8.定义脱敏工厂,根据字节码,创建对象。
9.定义脱敏符号Symbol,做替换
代码例子
1.定义一个脱敏器接口****Desensitization
/**
* 脱敏器
*
* @author zhangxiaoxu15
* @date 2022/2/8 10:56
*/
public interface Desensitization<T> {
/**
* 脱敏实现
*
* @param target 脱敏对象
* @return 脱敏返回结果
*/
T desensitize(T target);
}
2.接口实现DefaultDesensitization
点击查看代码
/**
* 默认脱敏实现
*
* @author zhangxiaoxu15
* @date 2022/2/8 11:01
*/
public interface DefaultDesensitization extends Desensitization<String> {
}
3.手机号脱敏实现类MobileNoDesensitization
点击查看代码
/**
* 手机号脱敏器,保留前3位和后4位
*
* @author zhangxiaoxu15
* @date 2022/2/8 11:02
*/
public class MobileNoDesensitization implements DefaultDesensitization {
/**
* 手机号正则
*/
private static final Pattern DEFAULT_PATTERN = Pattern.compile("(13[0-9]|14[579]|15[0-3,5-9]|16[6]|17[0135678]|18[0-9]|19[89])\\d{8}");
/**
* 手机号脱敏实现
* @param target 脱敏对象
* @return
*/
@Override
public String desensitize(String target) {
Matcher matcher = DEFAULT_PATTERN.matcher(target);
while (matcher.find()) {
String group = matcher.group();
target = target.replace(group, group.substring(0, 3) + Symbol.getSymbol(4, Symbol.STAR) + group.substring(7, 11));
}
return target;
}
}
4.定义一个脱敏注解Desensitize
点击查看代码
/**
* 脱敏注解
*
* @author zhangxiaoxu15
* @date 2022/2/8 11:09
*/
@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})//字段、枚举的常量,注解
@Retention(RetentionPolicy.RUNTIME)//运行时执行
@JacksonAnnotationsInside//组合注解,自定义注解需要
@JsonSerialize(using = ObjectDesensitizeSerializer.class)//指定序列化实现对象
@Documented
public @interface Desensitize {
/**
* 对象脱敏器实现
*/
@SuppressWarnings("all")
Class<? extends Desensitization<?>> desensitization();
}
5.定义默认的脱敏注解DefaultDesensitize
点击查看代码
/**
* 默认脱敏注解
*
* @author zhangxiaoxu15
* @date 2022/2/8 11:14
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@Desensitize(desensitization = DefaultDesensitization.class)
@Documented
public @interface DefaultDesensitize {
}
6.定义手机号脱敏注解MobileNoDesensitize
/**
- 手机号脱敏注解
- @author zhangxiaoxu15
- @date 2022/2/8 11:18
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@Desensitize(desensitization = MobileNoDesensitization.class)
@Documented
public @interface MobileNoDesensitize {
}
7.定义脱敏序列化器,createContextual方法通过它注入新增的序列化对象。serialize方法,来做序列化转化
点击查看代码
/**
* 脱敏序列化器
*
* StdSerializer:所有标准序列化程序所使用的基类,这个是编写自定义序列化程序所推荐使用的基类。
* ContextualSerializer:是Jackson 提供的另一个序列化相关的接口,它的作用是通过字段已知的上下文信息定制JsonSerializer。
* @author zhangxiaoxu15
* @date 2022/2/8 11:10
*/
public class ObjectDesensitizeSerializer extends StdSerializer<Object> implements ContextualSerializer {
private static final long serialVersionUID = -7868746622368564541L;
private transient Desensitization<Object> desensitization;
protected ObjectDesensitizeSerializer() {
super(Object.class);
}
public Desensitization<Object> getDesensitization() {
return desensitization;
}
public void setDesensitization(Desensitization<Object> desensitization) {
this.desensitization = desensitization;
}
/**
* 扫描到的进入序列化
* createContextual可以获得字段的类型以及注解。当字段拥有自定义注解时,取出注解中的值创建定制的序列化方式,这样在serialize方法中便可以得到这个值了。
* createContextual方法只会在第一次序列化字段时调用(因为字段的上下文信息在运行期不会改变),所以无需关心性能问题。
* @param prov
* @param property
* @return
*/
@Override
public JsonSerializer<Object> createContextual(SerializerProvider prov, BeanProperty property) {
//获取属性注解
Desensitize annotation = property.getAnnotation(Desensitize.class);
return createContextual(annotation.desensitization());
}
@SuppressWarnings("unchecked")
public JsonSerializer<Object> createContextual(Class<? extends Desensitization<?>> clazz) {
ObjectDesensitizeSerializer serializer = new ObjectDesensitizeSerializer();
if (clazz != DefaultDesensitization.class) {
serializer.setDesensitization((Desensitization<Object>) DesensitizationFactory.getDesensitization(clazz));
}
return serializer;
}
@Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException {
Desensitization<Object> objectDesensitization = getDesensitization();
if (objectDesensitization != null) {
try {
gen.writeObject(objectDesensitization.desensitize(value));
} catch (Exception e) {
gen.writeObject(value);
}
} else if (value instanceof String) {
gen.writeString(Symbol.getSymbol(((String) value).length(), Symbol.STAR));
} else {
gen.writeObject(value);
}
}
}
8.定义脱敏工厂,根据字节码,创建对象。
点击查看代码
/**
* 工厂方法
*
* @author zhangxiaoxu15
* @date 2022/2/8 10:58
*/
public class DesensitizationFactory {
private DesensitizationFactory() {
}
private static final Map<Class<?>, Desensitization<?>> map = new HashMap<>();
@SuppressWarnings("all")
/**
* 通过clazz求class对象
*/
public static Desensitization<?> getDesensitization(Class<?> clazz) {
if (clazz.isInterface()) {
throw new UnsupportedOperationException("desensitization is interface, what is expected is an implementation class !");
}
return map.computeIfAbsent(clazz, key -> {
try {
return (Desensitization<?>) clazz.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
throw new UnsupportedOperationException(e.getMessage(), e);
}
});
}
}
9.定义脱敏符号
点击查看代码
/**
* 脱敏符号
*
* @author zhangxiaoxu15
* @date 2022/2/8 10:53
*/
public class Symbol {
/**
* '*'脱敏符
*/
public static final String STAR = "*";
private Symbol() {
}
/**
* 获取符号
*
* @param number 符号个数
* @param symbol 符号
*/
public static String getSymbol(int number, String symbol) {
return IntStream.range(0, number).mapToObj(i -> symbol).collect(Collectors.joining());
}
}
10.定义java bean
点击查看代码
public class Person {
@MobileNoDesensitize
private String moblie;
@DefaultDesensitize
private String name;
public String getMoblie() {
return moblie;
}
public void setMoblie(String moblie) {
this.moblie = moblie;
}
public Person(String moblie, String name) {
this.moblie = moblie;
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
执行情况
点击查看代码
@SpringBootTest
class DemoTestApplicationTests {
/**
* 首先定义脱敏序列化实现类ObjectDesensitizeSerializer,主要作用是获取序列化对象和实现类
* 定义注解@Desensitize,指定序列化实现对象。绑定脱敏实现类
* 定义手机号脱敏注解@MobileNoDesensitize,绑定脱敏实现类
* @throws JsonProcessingException
*/
@Test
void contextLoads() throws JsonProcessingException {
Person person=new Person("15927388235","12121");
ObjectMapper objectMapper = new ObjectMapper();
//将对象序列化为json字符串
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); //忽略为null的字段
String userJsonString = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(person);
System.out.println(userJsonString);
System.out.println("11111111111");
}
}
点击查看代码
{
"moblie" : "159****8235",
"name" : "*****"
}