Jackson序列化方式实现数据字段脱敏(工厂模式 + 策略模式)

Jackson序列化方式实现数据脱敏


方案选择

  • 基于Mybatis 的拦截器:对select 语句进行拦截数据脱敏,但是存在问题 在某些业务中对数据脱敏字段是需要进行逻辑业务处理的
  • 基于Jaskson 序列化:针对需要展示在前端的数据 通常我们都是对应一个VO对象,这里可以在将VO对象返回给前端序列化时进行数据的脱敏

实现方案

1.自定义序列化器 覆盖原来的序列化方式
	需要引入jackson依赖
     <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
            <version>2.11.0</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.11.0</version>
        </dependency>


/**
 * 脱敏序列化器
 */
public class ObjectDesensitizeSerializer extends StdSerializer<Object> implements ContextualSerializer {

   
    protected ObjectDesensitizeSerializer() {
        super(Object.class);
    }

    @Override
    public JsonSerializer<Object> createContextual(SerializerProvider prov, BeanProperty property) {
        
        //  这里创建序列化上下文环境 以选择是否需要指定返回不同的序列化器
        
        
        return serializer;
    }

    @Override
    public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException {
       
        //  这里编写实际序列化的字段的处理
        
    }

}
2.定义策略类(处理实际脱敏逻辑)

然后针对不同的字段会有不同的脱敏方案 比如 身份证号 银行卡号 电话号码等

这里采用 策略模式 + 工厂模式 进行解耦,符合开闭原则

定义一个顶级接口 然后多个实现

/**
 * 定义一个 顶级的脱敏器
 */
public interface Desensitization<T> {


    /**
     * 脱敏实现
     *
     * @param target 脱敏对象
     * @return 脱敏返回结果
     */
    T desensitize(T target);

}

//  多接口实现

/**
 * 字符串脱敏器
 */
public interface StringDesensitization extends Desensitization<String> {
}

/**
 * 身份证(18位和15位) 脱敏器
 */
public class IDCardDesensitization implements StringDesensitization {
}


/**
 * 邮箱脱敏器 默认只保留域名
 */
public class EmailDesensitization implements StringDesensitization {
}

这里只展示具体的一个脱敏实现 其他都是类似

/**
 * 手机号脱敏器 默认只保留前3位和后4位
 */
public class PhoneDesensitization implements StringDesensitization {
    /**
     * 手机号正则
     */
    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}");
    /**
     * 手机号脱敏 只保留前3位和后4位
     */
    @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;
    }
}

public class Symbol {
    /**
     * '*'脱敏符
     */
    public static final String STAR = "*";

    private Symbol() {

    }

    /**
     * 获取符号
     *
     * @param number 符号个数
     * @param symbol 符号
     */
    public static String getSymbol(int number, String symbol) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < number; i++) {
            sb.append(symbol);
        }
        return sb.toString();
    }
}
3.定义工厂类 获取实际策略 并缓存策略

上述是 策略模式的体现 ,策略模式 通常结合工厂模式 屏蔽创建对象细节 直接通过工厂创建指定的策略类

/**
 *策略类的工厂
 */
public class DesensitizationFactory {

    private DesensitizationFactory() {
    }

    //  这里采用一个 Map 集合 对指定的策略类进行缓存 避免对象的重复创建
    private static final Map<Class<?>, Desensitization<?>> map = new HashMap<>();

    
    @SuppressWarnings("all")
    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, k -> {
            try {
                //  返回指定 Class 的策略类 同时缓存在 Map 中
                return (Desensitization<?>) clazz.newInstance();
            } catch (InstantiationException | IllegalAccessException e) {
                throw new UnsupportedOperationException(e.getMessage(), e);
            }
        });
    }

}
4.定义注解 简化使用

然后就是注解的定义,利用注解 直接对 VO 对象的字段标记,无代码入侵

/**
 * 对象脱敏 注解
 */
@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE}) // 作用于注解类型上 供其他注解使用
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside  //  组合注解 将多个注解拼在一起使用
@JsonSerialize(using = ObjectDesensitizeSerializer.class) //  标记序列化配置中 使用哪个类序列化 这里指定之前定义的ObjectDesensitizeSerializer
@Documented
public @interface Desensitize {
    
    //  这里对应了工厂类中的Class类型 以及在 ObjectDesensitizeSerializer 中创建上下文环境时 可供获取的参数去选择实际的脱敏方式
    
    /**
     * 对象脱敏器实现
     */
    @SuppressWarnings("all")
    Class<? extends Desensitization<?>> desensitization();


}

然后就是对应的实际字段注解

/**
 * 电话脱敏 注解
 */
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@Desensitize(desensitization = PhoneDesensitization.class)  //  这里指定执行脱敏逻辑的类
@Documented
public @interface PhoneDesensitize {
}

/**
 * 中华人民共和国身份证 脱敏注解
 */
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@Desensitize(desensitization = IDCardDesensitization.class) //  这里指定执行脱敏逻辑的类
@Documented
public @interface IDCardDesensitize {
}
5.完善自定义序列化器的逻辑

最后就是回到开头 编写实际的 自定义序列化器

**
 * 脱敏序列化器
 */
public class ObjectDesensitizeSerializer extends StdSerializer<Object> implements ContextualSerializer {
    
    
	//  面向接口 根据策略不同而实际 set 不同的 脱敏类
    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;
    }

    //  创建上下文环境
    @Override
    public JsonSerializer<Object> createContextual(SerializerProvider prov, BeanProperty property) {
        //  根据 BeanProperty 获取被标记 VO 字段的注解上的 实际策略脱敏类
        Desensitize annotation = property.getAnnotation(Desensitize.class);
        return createContextual(annotation.desensitization());
    }

    @SuppressWarnings("unchecked")
    public JsonSerializer<Object> createContextual(Class<? extends Desensitization<?>> clazz) {
        ObjectDesensitizeSerializer serializer = new ObjectDesensitizeSerializer();
        
        //  判断是否属于 StringDesensitization 因为 StringDesensitization 属于全脱敏
        if (clazz != StringDesensitization.class) {
            
            //  不属于则 回到上面 通过工厂类去创建脱敏类
            serializer.setDesensitization((Desensitization<Object>) DesensitizationFactory.getDesensitization(clazz));
        }
        return serializer;
    }

    
    //  创建完 上下文环境 返回 serializer 执行序列化 serialize
    @Override
    public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException {
        Desensitization<Object> objectDesensitization = getDesensitization();
        
        //  获取 策略类(即 前面 set 的 desensitization)  
        
        
        if (objectDesensitization != null) {
            try {
                //  不为空调用处理脱敏逻辑
                gen.writeObject(objectDesensitization.desensitize(value));
            } catch (Exception e) {
                gen.writeObject(value);
            }
        } else if (value instanceof String) {
            //  为空 说明是 StringDesensitization  且字段是 String 类型 则 全脱敏
            gen.writeString(Symbol.getSymbol(((String) value).length(), Symbol.STAR));
        } else {
            //  否则 正常序列化
            gen.writeObject(value);
        }
    }
}

最终可达到 一个注解 即可脱敏

在这里插入图片描述

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值