当前位置:   article > 正文

接口返回数据字段脱敏处理方案_fastjson 数据脱敏

fastjson 数据脱敏

1.自定义注解Jackson序列化实现脱敏

1.自定义注解类

@Target({ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@JacksonAnnotationsInside
@JsonSerialize(using = DataMaskingSerializer.class)
public @interface DataMask {
    DataMaskEnum function() default DataMaskEnum.EMAIL;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

2.定义策略枚举

public enum DataMaskEnum {
    /**
     * Email sensitive type.
     */
    EMAIL(s -> DataMaskContentUtil.getMaskToEmail(s));
    /**
     * 成员变量  是一个接口类型
     */
    private Function<String, String> function;
    DataMaskEnum(Function<String, String> function) {
        this.function = function;
    }
    public Function<String, String> function() {
        return this.function;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  1. 序列化触发内容格式化类 继承 JsonSerializer (Jackjson序列化器),实现序列化器上下文接口 ,重新序列化方法,创建序列器上下文方法中处理并设置处理内容,这里是先获取属性的自定义注解,并获取注解中定义的策略,从根据策略格式化内容并写入到JSON生成器中,
public class DataMaskingSerializer extends JsonSerializer<String> implements ContextualSerializer {

    private DataMaskEnum dataMaskEnum;

    @Override
    public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        gen.writeString(dataMaskEnum.function().apply(value));
    }

    @Override
    public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
        DataMask annotation = property.getAnnotation(DataMask.class);
        if (Objects.nonNull(annotation)&&Objects.equals(String.class, property.getType().getRawClass())) {
            this.dataMaskEnum = annotation.function();
            return this;
        }
        return prov.findValueSerializer(property.getType(), property);
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

4.Jackson 序列化触发工具类

public class JsonUtils {
    private static final Logger log = Logger.getLogger(JsonUtils.class);

    final static ObjectMapper objectMapper;

    static {
        objectMapper = new ObjectMapper();
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
        objectMapper.setTimeZone(TimeZone.getTimeZone("GMT+8"));
    }

    public static ObjectMapper getObjectMapper() {
        return objectMapper;
    }

    /**
     * JSON串转换为Java泛型对象
     *
     * @param <T>
     * @param jsonString JSON字符串
     * @param tr         TypeReference,例如: new TypeReference< List<FamousUser> >(){}
     * @return List对象列表
     */
    public static <T> T toGenericObject(String jsonString, TypeReference<T> tr) {

        if (jsonString == null || "".equals(jsonString)) {
            return null;
        } else {
            try {
                return (T) objectMapper.readValue(jsonString, tr);
            } catch (Exception e) {
                log.warn(jsonString);
                log.warn("json error:" + e.getMessage());
            }
        }
        return null;
    }

    /**
     * Json字符串转Java对象
     *
     * @param jsonString
     * @param c
     * @return
     */
    public static Object toObject(String jsonString, Class<?> c) {

        if (jsonString == null || "".equals(jsonString)) {
            return "";
        } else {
            try {
                return objectMapper.readValue(jsonString, c);
            } catch (Exception e) {
                log.warn("json error:" + e.getMessage());
            }
        }
        return "";
    }

    /**
     * Java对象转Json字符串
     *
     * @param object Java对象,可以是对象,数组,List,Map等
     * @return json 字符串
     */
    public static String toJson(Object object) {
        String jsonString = "";
        try {
            jsonString = objectMapper.writeValueAsString(object);
        } catch (Exception e) {
            e.printStackTrace();
            log.warn("json error:" + e.getMessage());
        }
        return jsonString;
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78

5.注解引用,新建实体对象 并且在属性上引用注解并指定策略

@Data
public class DataMaskDTO {
    @DataMask(function=DataMaskEnum.EMAIL)
    private String email;
}
  • 1
  • 2
  • 3
  • 4
  • 5

6.验证结果,新建实体类并调用 json工具类就可以了

@Slf4j
public class DataMaskTest {

    @Test
    public void maskCommitDTO(){

        DataMaskDTO dataMaskDTO = new DataMaskDTO();
        dataMaskDTO.setEmail("1234444@163.com");
        log.info("jsonMaskContent :{}", JsonUtils.toJson(dataMaskDTO));
        log.info("old : {}",dataMaskDTO);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

2.fastjson序列化方式实现脱敏

首先看一下JSONField元注解的源码,这里只需要用到序列化

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER })
public @interface JSONField {
    /**
     * config encode/decode ordinal
     * @since 1.1.42
     * @return
     */
    int ordinal() default 0;

    String name() default "";

    String format() default "";

    boolean serialize() default true;

    boolean deserialize() default true;

    SerializerFeature[] serialzeFeatures() default {};

    Feature[] parseFeatures() default {};
    
    String label() default "";
    
    /**
     * @since 1.2.12
     */
    boolean jsonDirect() default false;
    
    /**
     * Serializer class to use for serializing associated value.
     * 
     * @since 1.2.16
     */
    Class<?> serializeUsing() default Void.class;
    
    /**
     * Deserializer class to use for deserializing associated value. 
     * 
     * @since 1.2.16 
     */
    Class<?> deserializeUsing() default Void.class;

    /**
     * @since 1.2.21
     * @return the alternative names of the field when it is deserialized
     */
    String[] alternateNames() default {};

    /**
     * @since 1.2.31
     */
    boolean unwrapped() default false;
    
   /**
    * Only support Object
    * 
    * @since 1.2.61
    */
   String defaultValue() default "";
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61

2.以上注解属性还有个序列化(serializeUsing)和反序列化(deserializeUsing)
这两个属性会在目标对象执行 JSON.toJSONString时触发,从而属性值进行序列化处理,JSON.parseObject 触发反序列化操作。

由于业务场景比较复杂,正则不支持,所以我们需要再序列化里加入自定义格式化内容处理。
1.新建一个Java类,实现ObjectSerializer接口,重写write 方法,serializer 是序列化对象,object 是属性值,这里我们只需要关注这两个属性,格式化object 内容再写入serializer中就可以了

public class EmailMaskSerializer implements ObjectSerializer {


    @Override
    public void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType, int features) throws IOException {
       //处理object内容
      Object o = MaskUtil.pase(object);;
       serializer.write(o);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

2.调用方式 新建实体类 在字段上加上JSONField注解,并且serializeUsing 指向 我们写好的内容处理类EmailMaskSerializer

@Data
public class DataMaskDTO {
    @JSONField(name = "email",serializeUsing = EmailMaskSerializer.class)
    private String email;
}
  • 1
  • 2
  • 3
  • 4
  • 5

3.定义脱敏工具类

public class MaskUtils extends StringUtils {

    /**
     * 除邮箱格式后缀加密长度
     */
    public static final int EMAIL_LENGTH = 11;
    /**
     * 除邮箱格式后缀加密长度
     */
    public static final int EMAIL_MASK_HANDE = 3;
    /**
     * 除邮箱格式后缀加密长度
     */
    public static final int NAME_MASK_LENGTH = 3;


    /**
     * 返回用户名脱敏串
     *
     * @param name
     * @return
     */
    public static String getMaskToName(String name) {
        if (isBlank(name) || name.length() == 1) {
            return name;
        }
        int index = name.length() > 2 ? 1 : 0;
        return getMaskStr(name, 1, index);
    }

    /**
     * 返回用户名脱敏串
     *
     * @param phone
     * @return
     */
    public static String getMaskToPhone(String phone) {
        if (isBlank(phone) || phone.length() == 1) {
            return phone;
        }
        int index = phone.length() >= 11 ? 4 : 0;
        return getMaskStr(phone, 3, index);
    }

    /**
     * 返回邮箱脱敏串
     *
     * @param object
     * @return
     */
    public static String getMaskToEmail(Object object) {
        if (Objects.isNull(object)) {
            return null;
        }
        String email = object.toString();
        int emailFlagIndex = email.lastIndexOf("@");
        if (isBlank(email) || emailFlagIndex < 0) {
            return email;
        }
        //兼容非正常邮箱脱敏 例如1@163.com 或 @163.com等
        int handIndex = Math.min(emailFlagIndex, EMAIL_MASK_HANDE);
        int maskIndex = EMAIL_LENGTH - handIndex;
        String maskSb = String.format("%s%s%s", email.substring(0, handIndex), getMaskStrByLength(maskIndex), email.substring(emailFlagIndex));
        return maskSb;
    }

    /**
     * 通用脱敏方案
     *
     * @param str   脱敏内容
     * @param first 保留原本内容前?位
     * @param last  保留原本内容最后?位
     * @return
     */
    public static String getMaskStr(String str, Integer first, Integer last) {

        if (isBlank(str)) {
            return str;
        }
        StringBuffer sb = new StringBuffer();
        int index = str.length() - (first + last);
        sb.append(str.substring(0, first));
        sb.append(getMaskStrByLength(index));
        sb.append(str.substring(str.length() - last));
//        sb.append(getMaskToEmail(str));
        return sb.toString();
    }

    /**
     * 根据传入的个数返回*串字符
     *
     * @param index
     * @return
     */
    public static String getMaskStrByLength(Integer index) {
        if (index <= 0) {
            return "";
        }
        StringBuffer maskStr = new StringBuffer();
        for (int i = 0; i < index; i++) {
            maskStr.append("*");
        }
        return maskStr.toString();
    }


}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107

4.验证结果:

@Slf4j
public class DataMaskTest {
    @Test
    public void maskCommitDTO(){
        DataMaskDTO dataMaskDTO = new DataMaskDTO();
        dataMaskDTO.setEmail("1234444@163.com");
        String json = JSON.toJSONString(dataMaskDTO);
        log.info("jsonMaskContent :{}", json);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

3.自定义注解+过滤器实现脱敏

1.自定义注解类

@Target({ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface DataMask {
    DataMaskEnum function() default DataMaskEnum.EMAIL;
}
  • 1
  • 2
  • 3
  • 4
  • 5

2.定义策略枚举

public enum DataMaskEnum {

    /**
     * Email sensitive type.
     */
    EMAIL(s -> DataMaskContentUtil.getMaskToEmail(s));

    /**
     * 成员变量  是一个接口类型
     */
    private Function<String, String> function;

    DataMaskEnum(Function<String, String> function) {
        this.function = function;
    }

    public Function<String, String> function() {
        return this.function;
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

3.定义过滤器,实现ValueFilter过滤器,重新 process方法,在process方法中进行脱敏策略处理

@Component
@Slf4j
public class ValueMaskFilter implements ValueFilter {
    @Override
    public Object process(Object o, String name, Object value) {
        try {
            if (Objects.isNull(value) || !(value instanceof String)) {
                return value;
            }
            try {
                Field field = o.getClass().getDeclaredField(StringUtil.underlineToCamel(name));
                log.info("name :{}, value :{}", name, value);
                DataMask dataMask;
                if (String.class != field.getType() || (dataMask = field.getAnnotation(DataMask.class)) == null) {
                    return value;
                }
                String valueStr = value.toString();
                DataMaskEnum dataMaskEnum = dataMask.function();
                switch (dataMaskEnum) {
                    case EMAIL:
                        return MaskUtils.getMaskToEmail(valueStr);
                    case USERNAME:
                        return MaskUtils.getMaskToName(valueStr);
                    default:
                }
            } catch (NoSuchFieldException e) {
                return value;
            }
            return value;
        } catch (Exception e) {
            return value;
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

3.创建WebConfig配置类,将fastjson过滤器注册到容器中,这里只要关注 configureMessageConverters方法

@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
    static {
        //全局配置关闭 Fastjson 循环引用
        JSON.DEFAULT_GENERATE_FEATURE |= SerializerFeature.DisableCircularReferenceDetect.getMask();
    }
    

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(new ByteArrayHttpMessageConverter());
        converters.add(new StringHttpMessageConverter());
        final List<MediaType> supplyMediaTypes = Arrays.stream(
                new MediaType[]{MediaType.APPLICATION_JSON, MediaType.APPLICATION_OCTET_STREAM, MediaType.TEXT_PLAIN,
                        MediaType.ALL, MediaType.valueOf("application/vnd.git-lfs+json")}).collect(Collectors.toList());
        FastJsonHttpMessageConverter4 fastJsonHttpMessageConverter4 = new FastJsonHttpMessageConverter4();
        fastJsonHttpMessageConverter4.setDefaultCharset(defaultCharset);
        fastJsonHttpMessageConverter4.setSupportedMediaTypes(supplyMediaTypes);
        final FastJsonConfig config = new FastJsonConfig();
        config.setCharset(defaultCharset);
        config.setSerializerFeatures(Constants.SERIALIZER_FEATURES);
        config.getSerializeConfig().put(Object.class, DataMaskEmailSerializer.DATA_MASK_EMAIL_SERIALIZER);
        config.getSerializeConfig().put(Date.class, ForceDateCodec.INSTANCE);
        //自定义脱敏过滤器
				config.setSerializeFilters(new ValueMaskFilter());
        System.out.println(config.getSerializeConfig().get(String.class));
        fastJsonHttpMessageConverter4.setFastJsonConfig(config);
        converters.add(fastJsonHttpMessageConverter4);
        super.configureMessageConverters(converters);
    }


    @Override
    public Validator getValidator() {
        return null;
    }

    @Override
    public MessageCodesResolver getMessageCodesResolver() {
        return null;
    }


    private Charset defaultCharset = Charset.forName("UTF-8");
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

4.spring aop拦截器后置处理进行脱敏

1.自定义注解类

@Target({ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface DataMask {
    DataMaskEnum function() default DataMaskEnum.EMAIL;
}

2.定义策略枚举
public enum DataMaskEnum {

    /**
     * Email sensitive type.
     */
    EMAIL(s -> DataMaskContentUtil.getMaskToEmail(s));

    /**
     * 成员变量  是一个接口类型
     */
    private Function<String, String> function;

    DataMaskEnum(Function<String, String> function) {
        this.function = function;
    }

    public Function<String, String> function() {
        return this.function;
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

3.定义spring aop拦截器,针对前端请求进行拦截处理,这里着重注意@AfterReturning,在返回前端之前做处理
这里需要注意handleMaskValue方法,递归时对于对象类型判断,可能存在内存泄露的风险,所以在使用的时候根据情况对 UN_HANDLE_FILED_TYPE_LIST 集合进行补全即可。

package com.alibaba.force.api.component.aop;

import com.alibaba.force.api.component.authentication.aop.InterceptorOrder;
import com.alibaba.force.common.diamond.DiamondConfigConstants;
import com.alibaba.force.common.diamond.DiamondConfigService;
import com.alibaba.force.common.enums.AppDeployEnvEnum;
import com.alibaba.force.common.model.PageResult;
import com.alibaba.force.common.util.CollectionUtils;
import com.alibaba.force.common.util.PropertyUtils;
import com.alibaba.force.common.util.mask.DataMask;
import com.alibaba.force.common.util.mask.DataMaskContentUtil;
import com.alibaba.force.common.util.mask.DataMaskEnum;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.springframework.core.Ordered;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;

/**
 * 接口返回参数脱敏拦截器
 *
 * @author jiangzhiming
 */
@Slf4j
@Aspect
@Service
@Component
public class DataSourceMaskValueInterceptor implements Ordered {

    //递归深度 以防内存溢出风险
    private static int num = 20;

    private static int startNum = 1;

    @Pointcut(value = "execution(* com.alibaba.force..api..*(..)) && @annotation(com.alibaba.force.common.util.mask.DataMask)")
    public void dataSourceMaskValuePointCutRest() {
    }

    /**
     * 需要略过的字段类型(基本数据类型)
     */
    public static final List<Object> UN_HANDLE_FILED_TYPE_LIST = Arrays.asList(
            int.class, float.class, double.class, long.class,
            short.class, byte.class, boolean.class, char.class);

    /**
     * 脱敏开关 true 开启 false 关闭
     */
    public Boolean getMaksEnabled() {
        Optional<String> maskEnabled = DiamondConfigService.getConfig(DiamondConfigConstants.MASK_ENABLED);
        return maskEnabled.isPresent() && Boolean.parseBoolean(maskEnabled.get());
    }

    /**
     * 后置处理返回参数
     */
    @AfterReturning(value = "dataSourceMaskValuePointCutRest()", returning = "result")
    public Object afterReturning(JoinPoint joinPoint, Object result) throws Throwable {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        try {
            DataMask dataMask = method.getAnnotation(DataMask.class);
            log.info("dataSourceMaskValuePointCutRest: methodSignatureName={},dataMask:{},getMaksEnabled:{}", method.getName(), dataMask, getMaksEnabled());
            if (getMaksEnabled() && dataMask != null && checkedAppDeployEnv()) {
                if (result instanceof ResponseEntity) {
                    ResponseEntity resultEntity = (ResponseEntity) result;
                    Object resultData = resultEntity.getBody();
                    handleObject(startNum, resultData);
                }
            }
            return joinPoint.getThis();
        } catch (Exception e) {
            return joinPoint.getThis();
        } finally {
            restSum();
        }
    }

    /**
     * 对象脱敏处理
     *
     * @param resultData
     */
    public void handleObject(int thisNum, Object resultData) {
        try {
            //递归最大深度时 直接终止
            if (thisNum >= num) {
                return;
            }
            if (resultData instanceof List) {
                List<Object> resultList = (List) resultData;
                if (CollectionUtils.isNotEmpty(resultList)) {
                    for (Object obj : resultList) {
                        handleMaskValue(sumByAdd(thisNum), obj);
                    }
                }
            } else if (resultData instanceof PageResult) {
                //分页情况
                PageResult pageResult = (PageResult) resultData;
                List<Object> resultList = (List<Object>) pageResult.getList();
                if (CollectionUtils.isNotEmpty(resultList)) {
                    handleObject(sumByAdd(thisNum), resultList);
                }
            } else {
                //处理单个对象情况
                handleMaskValue(sumByAdd(thisNum), resultData);
            }
        } catch (Exception e) {
        } finally {
            sumBySubtract(thisNum);
        }
    }


    /**
     * 递归对象属性 处理属性复杂的对象
     *
     * @param obj
     * @param
     */
    public void handleMaskValue(int thisNum, Object obj) {
        try {
            if (Objects.isNull(obj) || isBasicDataType(obj)) {
                return;
            }
            if (obj instanceof Map) {
                Map<String, Object> objectMap = (Map<String, Object>) obj;
                for (String key : objectMap.keySet()) {
                    Object value = objectMap.get(key);
                    if (value instanceof String) {
                        objectMap.put(key, getMaskValue(value.toString(), getMaskType(key)));
                    } else {
                        handleObject(sumByAdd(thisNum), value);
                    }
                }
            } else if (obj instanceof List) {
                List<Object> resultList = (List) obj;
                if (CollectionUtils.isNotEmpty(resultList)) {
                    handleObject(sumByAdd(thisNum), resultList);
                }
            } else {
                DataMask dataMask;
                List<Field> fields = getFields(obj.getClass());
                for (Field field : fields) {
                    field.setAccessible(true);
                    if (String.class == field.getType()) {
                        if ((dataMask = field.getAnnotation(DataMask.class)) != null) {
                            //如果属性类型是时间类型,取出属性的值
                            String valueStr = (String) field.get(obj);
                            DataMaskEnum dataMaskEnum = dataMask.function();
                            field.set(obj, getMaskValue(valueStr, dataMaskEnum));
                        }
                        continue;
                    }
                    //基本类型 不包含字符串 为class对象时
                    Object object = field.get(obj);
                    if (!isBasicDataType(object)) {
                        handleObject(sumByAdd(thisNum), object);
                    }
                }
            }
        } catch (Exception e) {
        }
    }

    /**
     * 判断对象是否为基本数据类型
     *
     * @param clazz
     * @return
     */
    public boolean isBasicDataType(Object clazz) {
        //是否包装类 这个需要单独判断 防止死循环造成内存溢出
        if (clazz == Integer.class || clazz == Float.class || clazz == Double.class ||
                clazz == Long.class || clazz == Short.class || clazz == Byte.class ||
                clazz == Boolean.class || clazz == Character.class ||
                clazz == Date.class || clazz == java.sql.Date.class || clazz == Enum.class) {
            return true;
        }
        if (clazz instanceof Integer || clazz instanceof Float || clazz instanceof Double ||
                clazz instanceof Long || clazz instanceof Short || clazz instanceof Byte ||
                clazz instanceof Boolean || clazz instanceof Character || clazz instanceof Date ||
                clazz instanceof java.sql.Date || clazz instanceof Enum) {
            return true;
        }

        if (clazz instanceof Logger) {
            return true;
        }
        return UN_HANDLE_FILED_TYPE_LIST.contains(clazz);
    }

    /**
     * 类型为map时 需要进行字段模糊匹配,以防遗漏,这里目前还有一定缺陷,不能做到区别脱敏,目前是全脱敏
     *
     * @param fieldName
     * @return
     */
    public DataMaskEnum getMaskType(String fieldName) {
        if (StringUtils.isNotBlank(fieldName)) {
            if (fieldName.toLowerCase().contains(DataMaskEnum.EMAIL.name().toLowerCase())) {
                return DataMaskEnum.EMAIL;
            }
        }
        return null;
    }

    /**
     * 根据脱敏类型获取脱敏内容
     *
     * @param value
     * @param dataMaskEnum
     * @return
     */
    public String getMaskValue(String value, DataMaskEnum dataMaskEnum) {
        if (dataMaskEnum != null) {
            switch (dataMaskEnum) {
                case EMAIL:
                    return DataMaskContentUtil.getMaskToEmail(value);
                default:
                    return value;
            }
        }
        return value;
    }

    /**
     * 获取对象的所以字段 包括继承的父类字段
     *
     * @param c
     * @return
     */
    private List<Field> getFields(Class c) {
        List<Field> fields = new ArrayList<>();
        while (c != null) {
            fields.addAll(Arrays.asList(c.getDeclaredFields()));
            c = c.getSuperclass();
        }
        return fields;
    }

    private static int sumByAdd(int n) {
        return n == startNum ? 1 : ++n;
    }

    private static int sumBySubtract(int n) {
        return n == num ? num : --n;
    }

    private static void restSum() {
        num = 20;
    }

    /**
     * 校验部署环境
     *
     * @return
     */
    private boolean checkedAppDeployEnv() {
        AppDeployEnvEnum currentDeployEnv = PropertyUtils.getAppDeployEnv();
        //获取当前部署环境,针对特定的环境进行处理
        if (AppDeployEnvEnum.ALIYUN.equals(currentDeployEnv)) {
            return true;
        }
        return false;
    }

    @Override
    public int getOrder() {
        return InterceptorOrder.MASK_DATA_SOURCE;
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284

最后的方案采用

第一种方案,对fastjson不起作用,并且带有一定的侵入,所以不采取该方案。

第二种方案,可能内部存在序列化和反序列化场景,会影响内部的业务,故不采用该方案。

第三种方案,DTO层比较混乱,而且过滤器在webconfig注册后未生效,原因还在排查中。

第四种方案, 增加aop拦截器,在拦截器中的后置处理器中,对返回的参数属性进行解析,带有自定义注解的参数进行拦截,这里兼容了层级复杂多类型的数据结构,不过递归时可能存在内存溢出风险,只要把对象类型的判断处理好就能避免内存泄漏情况。这里为了方便控制脱敏效果,增加了脱敏开关和目标方法进行双重控制,降低对不需要脱敏的结果造成干扰,同时也省去不必要的处理。还需要注意的是,map类型参数无法做到指定脱敏,这里对map key进行了遍历模糊匹配,如果匹配到策略,则会采用脱敏策略对参数进行处理。

目前采取第四种方案。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小桥流水78/article/detail/920832
推荐阅读
相关标签
  

闽ICP备14008679号