背景
由于项目中涉及到用户敏感信息,如姓名、证件、手机号等。这些敏感信息再某些业务条件下不允许直接看到明文,所以需要对接口的出参做脱敏或者加密处理。
解决方案
项目默认使用jackson作为序列化工具,jackson提供了可自定义序列化方式的拓展点,自定义序列方式可以使得序列化的结果可以按照我们所需要的结果输出。基于这点我们可以自定义加密或者脱敏注解来达到想要的效果。
具体实现
1、创建加密或脱敏序列化注解
2、自定义序列化器CustomizeFieldSerializer类用于处理自定义序列化需求
3、创建序列化拦截器CustomizeAnnotationIntrospector用于查找是否包含自定义序列化注解,如果包含自定义序列化注解则走自定义序列化器
4、创建自定义序列化注解处理类
5、将CustomizeAnnotationIntrospector注册到ObjectMapper之中
序列化注解FieldSerialize
package com.xmair.framework.serializer;
import java.lang.reflect.Field;
public interface FieldSerialize<T> {
/**
* 序列化方法
* @param o 原始对象
* @param field 自定义注解字段
* @param an 自定义注解
*/
Object serialize(Object o, Field field, T an);
}
列化器CustomizeFieldSerializer
package com.xmair.framework.serializer;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonStreamContext;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.xmair.core.util.ObjectUtil;
import com.xmair.framework.common.SpringBeanTools;
import org.apache.commons.collections4.CollectionUtils;
import org.bouncycastle.util.Arrays;
import org.springframework.util.ReflectionUtils;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Objects;
/**
* @Author: ljw
* @Date: 2023-9-22 21:43
*/
public class CustomizeFieldSerializer extends JsonSerializer<Object> {
@Override
public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
if(Objects.isNull(o) || Objects.isNull(jsonGenerator) || Objects.isNull(serializerProvider)
|| Objects.isNull(jsonGenerator.getCurrentValue()) || Objects.isNull(jsonGenerator.getOutputContext())){
return;
}
//原始对象类型
Class orginObjType = jsonGenerator.getCurrentValue().getClass();
//属性名称
JsonStreamContext outputContext = jsonGenerator.getOutputContext();
//取属性名对应字段
Field field = ReflectionUtils.findField(orginObjType, outputContext.getCurrentName());
//自定义序列化对象
List<FieldSerialize> fieldSerializes = SpringBeanTools.getBeanList(FieldSerialize.class);
if(CollectionUtils.isNotEmpty(fieldSerializes)){
for (FieldSerialize fieldSerialize : fieldSerializes) {
Type[] generic = ObjectUtil.getGeneric(fieldSerialize.getClass(), FieldSerialize.class);
if(!Arrays.isNullOrEmpty(generic)){
Type type = generic[0];
Annotation[] annotations = field.getAnnotations();
if(!Arrays.isNullOrEmpty(annotations)){
for (Annotation annotation : annotations) {
if(annotation.annotationType().equals(type)){
Object serialize = fieldSerialize.serialize(o,field,annotation);
jsonGenerator.writeObject(serialize);
return;
}
}
}
}
}
}
jsonGenerator.writeObject(o);
}
}
序列化拦截器CustomizeAnnotationIntrospector
package com.xmair.framework.serializer;
import com.fasterxml.jackson.databind.introspect.Annotated;
import com.fasterxml.jackson.databind.introspect.NopAnnotationIntrospector;
import com.xmair.framework.annotation.JsonSerialize;
import java.lang.annotation.Annotation;
import java.util.Objects;
/**
* @Author: ljw
* @Date: 2023-9-22 21:42
*/
public class CustomizeAnnotationIntrospector extends NopAnnotationIntrospector {
@Override
public Object findSerializer(Annotated am) {
try{
Iterable<Annotation> annotations = am.annotations();
if(Objects.nonNull(annotations)){
for (Annotation annotation : annotations) {
boolean hasCustomizeJsonSerializeAnnotation = hasCustomizeJsonSerializeAnnotation(annotation);
if(hasCustomizeJsonSerializeAnnotation){
//如果包含自定义加密注解则走自定义序列化器
return CustomizeFieldSerializer.class;
}
}
}
}catch (Exception e){
//查找自定义序列化器失败不报错,使用默认序列化器
}
return super.findSerializer(am);
}
//遍历查找是否包含自定义序列化注解
private boolean hasCustomizeJsonSerializeAnnotation(Annotation annotation) {
if(Objects.isNull(annotation)){
return false;
}
Class<? extends Annotation> aClass = annotation.annotationType();
Annotation[] annotations1 = aClass.getAnnotations();
for(Annotation annotation2 : annotations1){
if(annotation2 instanceof JsonSerialize){
return true;
}
}
return false;
}
}
脱敏注解@DesensitizeField
package com.xmair.framework.serializer;
import java.lang.annotation.*;
/**
* 脱敏字段注解,用*替换脱敏数据 123456 ---》 1****6
* */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@JsonSerialize
public @interface DesensitizeField {
String start() default "1";
String end() default "1";
}
脱敏注解处理类DesensitizeSerializer
package com.xmair.framework.serializer;
import com.xmair.framework.helper.DesensitizeUtil;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
/**
* @Author: ljw
* @Date: 2024-11-21 9:12
*/
@Component
public class DesensitizeSerializer implements FieldSerialize<DesensitizeField>{
@Override
public Object serialize(Object o, Field field,DesensitizeField an) {
Integer end = getExpressionValue(an.end(),field,o);
Integer start = getExpressionValue(an.start(),field,o);
return desensitize(o,start,end);
}
public static Object desensitize(Object data, Integer prefixNum, Integer suffixNum) {
Class<?> aClass = data.getClass();
if(aClass.isAssignableFrom(Collection.class)){
Collection list = (Collection) data;
if(!CollectionUtils.isEmpty(list)){
List tempList = new ArrayList<>();
for (Object o : list) {
if(o instanceof String) {
tempList.add(desensitize(o.toString(),prefixNum,suffixNum));
}
}
list = tempList;
}
return list;
}else{
return DesensitizeUtil.desensitize(data.toString(),prefixNum,suffixNum);
}
}
//获取脱敏起始位置
public static Integer getExpressionValue(String expressionStr, Field field, Object o){
try{
field.setAccessible(true);
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression(expressionStr);
StandardEvaluationContext context = new StandardEvaluationContext();
Object value = field.get(o);
if(Objects.nonNull(value)){
context.setVariable(field.getName(),value);
return (Integer) expression.getValue(context);
}
}catch (Exception e){
}
return 1;
}
}
注册拦截器
项目中还有自定的时间序列化,和上述脱敏序列化实现方式是一致的。其实上面提供了思路,还可以做更多的自定义序列化操作,如自定义序列化枚举值注解、加密注解等。
效果
未加注解
加上脱敏注解