Java数据脱敏框架_蒋蒋-CSDN博客_java 数据脱敏

数据脱敏框架

可以解决脱敏实体对象的循环依赖(控制遍历的深度),和实体相互引用的脱敏。

实现

定义敏感类型

/**
 * 敏感数据类型
 * @author jiangwenjie
 * @date 2021/1/26
 */
public enum EnumSensitiveType {
    /**
     * 标志这个是一个需要脱敏的实体类
     */
    ENITTY_CLASS,
    /**
     * 中文名
     */
    CHINESE_NAME,
    /**
     * 身份证号
     */
    ID_CARD,
    /**
     * 手机号
     */
    MOBILE_PHONE,
    /**
     * 地址
     */
    ADDRESS,
    /**
     * 电子邮件
     */
    EMAIL,
    /**
     * 银行卡
     */
    BANK_CARD;

}

定义注解

/**
 * 敏感数据脱敏注解
 *
 *  @author jiangwenjie
 * @date 2021/1/26
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Desensitized {

    /**
     * 脱敏类型 {@EnumSensitiveType}
     */
    EnumSensitiveType type();
}

定义工具类

/**
 * 敏感数据脱敏处理工具类
 * @author jiangwenjie
 * @date 2021/1/26
 */
@Slf4j
public class DesensitizedUtils {

    /**
     * 递归深度计数器:防止循环依赖
     */
    private static ThreadLocal<AtomicInteger> threadLocalCounter = new ThreadLocal<>();

    /**
     * 递归的最大深度
     */
    private static final Integer MAX_DEPTH = 3;

    /**
     * 敏感数据脱敏
     * 脱敏对象 的字段 需要添加 {@Sensitive} 注解并标注敏感类型
     * @param bean
     * @return
     */
    public static Object desensitization(Object bean) {
        try {
            // 控制递归的深度:防止循环依赖
            threadLocalCounter.set(new AtomicInteger(MAX_DEPTH));

            doDesensitization(bean);
        } catch (Exception e) {
            log.warn("数据脱敏工具异常",e);
        }finally {
            threadLocalCounter.remove();
        }
        return bean;
    }

    /**
     * 遍历递归脱敏操作
     * @param bean
     * @throws Exception
     */
    private static void doDesensitization(Object bean) throws Exception {
        if (Objects.isNull(bean) || isBeyoudMaxDepth(threadLocalCounter.get())) {
            return;
        }
        //处理子属性,包括集合中的
        Object object = null;
        if (bean.getClass().isArray()) {
            //对数组类型的字段进行递归过滤
            for (int i = 0; i < Array.getLength(bean); i++) {
                object = Array.get(bean, i);
                if (isNotGeneralType(object.getClass())) {
                    desensitizationField(object);
                }
            }
        } else if (bean instanceof Collection<?>) {
            //对集合类型的字段进行递归过滤
            Iterator<?> it = ((Collection<?>) bean).iterator();
            while (it.hasNext()) {
                object = it.next();
                if (isNotGeneralType(object.getClass())) {
                    desensitizationField(object);
                }
            }
        } else if (bean instanceof Map<?, ?>) {
            //对Map类型的字段进行递归过滤
            Map<?, ?> map = (Map<?, ?>) bean;
            for (Object o : map.entrySet()) {
                object = ((Map.Entry<?, ?>) o).getValue();
                if (isNotGeneralType(object.getClass())) {
                    desensitizationField(object);
                }
            }
        } else {//自定义类等
            if (isNotGeneralType(bean.getClass())) {
                desensitizationField(bean);
            }
        }
    }
    /**
     * 脱敏bean所有属性
     * @param bean
     * @throws Exception
     */
    private static void desensitizationField(Object bean) throws Exception{
        // 获取对象的所有属性
        List<Field> fields = getAllFields(bean);
        if (CollectionUtils.isEmpty(fields)){
            return;
        }
        Object value;
        for (Field field:fields) {
            field.setAccessible(true);
            value = field.get(bean);
            // 属性是final类型  不处理
            if (Objects.isNull(value)) {
                continue;
            }
            //不是基本数据类型
            if (isNotGeneralType(value.getClass())) {
                doDesensitization(value);
            }else{
                //脱敏操作
                setNewValueForField(bean, field, value);
            }
        }
    }

    /**
     * 获取包括父类所有 使用了{@Desensitized}注解的属性
     * @param objSource
     * @return
     */
    private static List<Field> getAllFields(Object objSource) {
        if(Objects.isNull(objSource)){
            return null;
        }
        AtomicInteger atomicInteger = new AtomicInteger(MAX_DEPTH);
        // 获得当前类的所有属性(private、protected、public)
        List<Field> fieldList = Lists.newArrayList();
        Class tempClass = objSource.getClass();
        while (Objects.nonNull(tempClass) && !tempClass.getName().toLowerCase().equals("java.lang.object") && !isBeyoudMaxDepth(atomicInteger)) {
            fieldList.addAll(Arrays.asList(tempClass.getDeclaredFields()));
            //得到父类,然后赋给自己
            tempClass = tempClass.getSuperclass();
        }
        // 过滤出来需要
        return fieldList.stream().filter(field -> !Modifier.isFinal(field.getModifiers()) && Objects.nonNull(field.getAnnotation(Desensitized.class))).collect(Collectors.toList());
    }

    /**
     * 排除基础类型、枚举类型、扩展库类型的字段
     * @param clazz
     * @return
     */
    private static boolean isNotGeneralType(Class<?> clazz) {
        return !clazz.isPrimitive()
                && !clazz.isEnum()
                && Objects.nonNull(clazz.getPackage())
                && !StringUtils.startsWith(clazz.getPackage().getName(), "javax.")
                && !StringUtils.startsWith(clazz.getPackage().getName(), "java.lang")
                && !StringUtils.startsWith(clazz.getPackage().getName(), "java.time");
    }

    /**
     * 脱敏操作(按照规则转化需要脱敏的字段并设置新值)
     * 目前只支持String类型的字段
     * @param bean
     * @param field
     * @param value
     * @throws IllegalAccessException
     */
    public static void setNewValueForField(Object bean, Field field, Object value) {
        try {
            //处理自身的属性
            Desensitized annotation = field.getAnnotation(Desensitized.class);
            String valueStr;
            if (!field.getType().equals(String.class) || Objects.isNull(value)
                    || StringUtils.isEmpty(valueStr = value.toString()) || Objects.isNull(annotation)) {
                return;
            }

            switch (annotation.type()) {
                case CHINESE_NAME: {
                    field.set(bean, chineseName(valueStr));
                    break;
                }
                case ID_CARD: {
                    field.set(bean, idCardNum(valueStr));
                    break;
                }
                case MOBILE_PHONE: {
                    field.set(bean, mobilePhone(valueStr));
                    break;
                }
                case ADDRESS: {
                    field.set(bean, address(valueStr, 7));
                    break;
                }
                case EMAIL: {
                    field.set(bean, email(valueStr));
                    break;
                }
                case BANK_CARD: {
                    field.set(bean, bankCard(valueStr));
                    break;
                } default:
                    break;
            }
        }catch (Exception e){
            log.warn("数据脱敏工具异常,bean:{},field:{},value:{}", GsonUtils.obj2Json(bean),field.getName(),value,e);
        }
    }

    private static Boolean isBeyoudMaxDepth(AtomicInteger atomicInteger){
        if (Objects.isNull(atomicInteger)){
            return Boolean.FALSE;
        }
        final int increment = atomicInteger.getAndDecrement();
        if (increment <= 0){
            return Boolean.TRUE;
        }
        return Boolean.FALSE;
    }


    /**
     * 【银行卡号】数据脱敏
     * 只留前四位和后四位
     * 6227 0383 3938 3938 393 脱敏结果: 6227 **** **** ***8 393
     *
     * @param value
     * @return
     */
    public static String bankCard(String value) {
        if (StringUtils.isEmpty(value)) {
            return null;
        }
        return StringUtils.left(value, 4).concat(StringUtils.removeStart(StringUtils.leftPad(StringUtils.right(value, 4), StringUtils.length(value), "*"), "***"));
    }

    /**
     * 【邮箱】数据脱敏
     * 邮箱前缀仅显示第一个字母,前缀其他隐藏,用星号代替,@及后面的地址显示
     * 例子:g**@163.com
     *
     * @param value
     * @return
     */
    public static String email(String value) {
        if (StringUtils.isEmpty(value)) {
            return null;
        }
        int index = StringUtils.indexOf(value, "@");
        if (index <= 1) {
            return value;
        } else {
            return StringUtils.rightPad(StringUtils.left(value, 1), index, "*").concat(
                    StringUtils.mid(value, index, StringUtils.length(value)));
        }
    }

    /**
     * 【身份证号】脱敏类型
     * 前3位,后4位
     * 130722199102323232 脱敏后: 130*************3232
     *
     * @param value
     * @return
     */
    public static String idCardNum(String value) {
        if (StringUtils.isEmpty(value)) {
            return null;
        }
        return StringUtils.left(value, 3).concat(StringUtils.removeStart(StringUtils.leftPad(StringUtils.right(value, 4), StringUtils.length(value), "*"), "***"));
    }

    /**
     * 【手机号码】数据脱敏
     * 18233583070 脱敏后: 182****3030
     *
     * @param value
     * @return
     */
    public static String mobilePhone(String value) {
        if (StringUtils.isEmpty(value)) {
            return null;
        }
        return StringUtils.left(value, 3).concat(StringUtils.removeStart(StringUtils.leftPad(StringUtils.right(value, 4), StringUtils.length(value), "*"), "***"));
    }

    /**
     * 【姓名】真实姓名脱敏
     * 中文姓名只显示最后一个汉字,其他隐藏为星号
     * 张三丰 :**丰
     *
     * @param value
     * @return
     */
    public static String chineseName(String value) {
        if (StringUtils.isEmpty(value)) {
            return null;
        }
        return StringUtils.leftPad(StringUtils.right(value, 1),StringUtils.length(value) , "*");
    }

    /**
     * 【地址】只显示到地区,不显示详细地址,比如:北京市海淀区****
     *
     * @param address
     * @param sensitiveSize 敏感信息长度
     * @return
     */
    public static String address(String address, int sensitiveSize) {
        if (StringUtils.isBlank(address) || address.length() <= sensitiveSize) {
            return address;
        }
        int length = StringUtils.length(address);
        return StringUtils.rightPad(StringUtils.left(address, length - sensitiveSize), length, "*");
    }
}

测试

定义一个四层的对象引用


    @Data
    static class EntityVo{
        @Desensitized(type = EnumSensitiveType.ENITTY_CLASS)
        List<Entity> list;
    }
    @Data
    static class Entity{
        @Desensitized(type = EnumSensitiveType.CHINESE_NAME)
        String name;
        @Desensitized(type = EnumSensitiveType.ENITTY_CLASS)
        List<A> list;
    }


    @Data
    static class A {
        @Desensitized(type = EnumSensitiveType.CHINESE_NAME)
        String name;
        @Desensitized(type = EnumSensitiveType.ENITTY_CLASS)
        B b;
    }
    @Data
    static class B {
        @Desensitized(type = EnumSensitiveType.CHINESE_NAME)
        String name;

        //A a;
    }
 public static void main(String[] args) {
        A a = new A();
        a.setName("慢慢的");
        B b = new B();
        b.setName("的地方");
        a.setB(b);

        Entity entity = new Entity();
        entity.setName("狭小的");
        entity.setList(Arrays.asList(a));
        EntityVo entityVo = new EntityVo();

        entityVo.setList(Arrays.asList(entity));
        DesensitizedUtils.desensitization(entityVo);
        System.out.println(GsonUtils.obj2Json(entityVo));

        /*A a = new A();
        a.setName("慢慢的");
        B b = new B();
        b.setName("的地方");
        a.setB(b);
        b.setA(a);
        DesensitizedUtils.desensitization(a);*/
        //System.out.println(GsonUtils.obj2Json(a));
        //System.out.println(a.hashCode());
    }

执行结果:
在这里插入图片描述

结论

上面utils类的可以写成AOP的方式,但是公司现在不允许再AOP所以使用了Utils方式,大家有改进建议欢迎留言,我会补充上去。


原网址: 访问
创建于: 2021-11-24 10:07:07
目录: default
标签: 无

请先后发表评论
  • 最新评论
  • 总共0条评论