在项目中经常使用到数据脱敏,这里使用 fasterxml.jackson来实现返回数据的脱敏操作
在这里借助 com.fasterxml.jackson.databind.JsonSerializer接口来完成对数据序列化的时候的操作
*引入包文件*
com.fasterxml.jackson.core jackson-core 2.11.4 com.fasterxml.jackson.core jackson-annotations 2.11.4 com.fasterxml.jackson.core jackson-databind 2.11.4 com.fasterxml.jackson.datatype jackson-datatype-jsr310 2.13.0 com.alibaba fastjson 1.2.76
一、创建策略
在序列化的时候需要指定序列化的规则,例如
字段长度 大于 10 前三后四明文字段长度 小于 4 无明文字段长度 大其他 后四明文
这里使用工具类来实现这个通用的操作
public class SensitiveUtils {
public static final String SYMBOL_STAR = "*";
public static String commonSensitive(String sensitiveValue) {
if (StringUtils.isBlank(sensitiveValue)) {
return sensitiveValue;
}
if (sensitiveValue.length() > 10) {
int s = sensitiveValue.length() - 7;
return sensitiveValue.replaceAll("(\w{3})\w{" + s + "}(\w{4})", "$1" + getStars(s) + "$2");
} else if (sensitiveValue.length() < 4) {
return getStars(sensitiveValue.length());
} else {
int s = sensitiveValue.length() - 4;
return sensitiveValue.replaceAll("\w{" + s + "}(\w{4})", getStars(s) + "$1");
}
}
public static boolean simpleInvalidSensitive(String value) {
if (StringUtils.isBlank(value)) {
return false;
}
return value.contains(SYMBOL_STAR);
}
public static String getStars(int s) {
String stars = StringUtils.EMPTY;
for (int i = 0; i < s; i++) {
stars += SYMBOL_STAR;
}
return stars;
}
}
上面的是一个通用的工具类,下面,我们使用接口来实例化这个通用的策略:
首先是接口:
public interface MyStrategy {
String desensitizationByPattern(String source);
}
//然后构建一个默认的策略:
public class MyDefaultStrategy implements MyStrategy {
public String desensitizationByPattern(String source) {
return SensitiveUtils.commonSensitive(source);
}
}
这个操作就是传入一个字段返回脱敏后的字符串,到这里就完成了一个策略的制定
二、构建序列化注解
这个注解用来明确字段序列化时需要的序列化器和使用的策略,这里要用到注解 @JsonSerialize,它的参数 using用来明确使用的序列化器,这里使用自定义的序列化器,下一节介绍序列化器的创建。
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = MySensitiveInfoSerialize.class)
@Inherited
public @interface CommonSensitive {
Class extends MyStrategy> strategy() default MyDefaultStrategy.class;
}
从上面可以看出来,这里的参数是一个接口性质的序列化策略,也就是说,后面是支持自定义的。
三、创建序列化器
序列化器要实现接口 com.fasterxml.jackson.databind.JsonSerializer
并实现方法
@Slf4j @NoArgsConstructor public class MySensitiveInfoSerialize extends JsonSerializerimplements ContextualSerializer { private MyStrategy myStrategy; public MySensitiveInfoSerialize(MyStrategy myStrategy) { this.myStrategy = myStrategy; } @Override public void serialize(String s, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { jsonGenerator.writeString(myStrategy.desensitizationByPattern(s)); } @Override public JsonSerializer> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException { if (beanProperty != null) { // 非 String 类直接跳过 if (Objects.equals(beanProperty.getType().getRawClass(), String.class)) { CommonSensitive sensitiveInfo = beanProperty.getAnnotation(CommonSensitive.class); if (sensitiveInfo == null) { sensitiveInfo = beanProperty.getContextAnnotation(CommonSensitive.class); } if (sensitiveInfo != null) { Class extends MyStrategy> clazz = sensitiveInfo.strategy(); // 如果能得到注解,就将注解的 value 传入 SensitiveInfoSerialize try { return new MySensitiveInfoSerialize(clazz.getDeclaredConstructor().newInstance()); } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { throw new RuntimeException(e); } } return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty); } } return serializerProvider.findNullValueSerializer(null); } }
serialize使用序列化器序列化字段
createContextual创建序列化上下文,主要是明确使用的序列化策略或者其他方式
到这里基本就可以使用了
四、自定义序列化策略
从上面我们的策略是用接口的方式使用的,这里要实现这个接口
public class MyCustomizeNameStrategy implements MyStrategy {
@Override
public String desensitizationByPattern(String source) {
if (StringUtils.isNotBlank(source)) {
int s = source.length();
int ss = source.lastIndexOf(".");
if (ss == -1) {
ss = 1;
int sss = s - ss;
return source.replaceAll("(\S{" + ss + "})\S{" + sss + "}", "$1" + SensitiveUtils.getStars(sss));
} else {
int sss = s - ss;
return source.replaceAll("\S{" + ss + "}(\S{" + sss + "})", SensitiveUtils.getStars(ss) + "$1");
}
} else {
return source;
}
}
}
这个是用来序列化姓名的,保留姓,这样就完成了,在使用注解 @CommonSensitive就可以使用了
五、规范化注解使用
在项目中经常要对不同性质的字段脱敏,例如邮箱,电话,身份证号码等,这些策略都不同,我们可以使用不同的序列化策略,但是这样使用不好的地方是我们要记住使用策略,为了方便使用,我们可以通过注解直接表明不同的注解表示不同的脱敏方式,无参的方式使用更加方便,这里选择使用邮件脱敏来说明
首先自定义一个邮件脱敏的规则:
public class MyEmailStrategy implements MyStrategy {
@Override
public String desensitizationByPattern(String source) {
if (StringUtils.isNotBlank(source)) {
int s = source.length();
int ss = source.indexOf("@");
int sss = s - ss;
return source.replaceAll("\S{" + ss + "}(\S{" + sss + "})", SensitiveUtils.getStars(ss)+"$1");
} else {
return source;
}
}
}
然后通过使用我们定义的原始注解包装一下
@Documented@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)@CommonSensitive(strategy = MyEmailStrategy.class)@JacksonAnnotationsInsidepublic @interface MyEmailSensitive {}
这样就完成了对字段属性的序列化注解,使用的策略正是上面我们定义的,这个注解还是无参的。
六、使用案例
先建立一个序列化对象
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@CommonSensitive(strategy = MyEmailStrategy.class)
@JacksonAnnotationsInside
public @interface MyEmailSensitive {
}
//上面我们使用不同的使用方式脱敏
//创建一个请求接口来实现脱敏操作
@RestController
@RequestMapping("/test")
@Slf4j
public class TestController {
@PostMapping("/submit2")
public Employee submit2(@RequestBody Employee employee) {
log.info(JSON.toJSONString(employee));
return employee;
}
}
测试使用报文:
{
"name": "王斌强",
"phone": "18637986236",
"email": "y.leffh@qymwmaej.yu"
}
打印日志:
{"email":"y.leffh@qymwmaej.yu","name":"王斌强","phone":"18637986236"}
返回报文:
{
"name": "王*",
"phone": "186****6236",
"email": "*******@qymwmaej.yu"
}
至此,完成了使用注解的方式完成字段的脱敏操作。



