最简单的脱敏方法

153 阅读2分钟

脱敏原理

  1. 一个http 请求从发送到响应最少会有 2 次序列化操作,第一次是请求进来服务器时,会将请求数据序列化成 Java 对象,第二次是服务器响应时再次进行序列化讲 Java 对象转成json 对象
  1. 由此可以在数据响应前,重写序列化方法就可以对返回的Java 对象的字段进行脱敏
  2. 确定了脱敏时机,那就还需确认哪些字段需要脱敏,如果系统命名规范的话,那就可以直接统一通过反射获取字段名称进行匹配。不过一般不会有的,每个程序员都有各自的代码风格,如 name、username、userName等。所以在这里通过注解的方式最为方便。
  3. 在使用注解时,就可以确定哪个字段使用哪个脱敏的策略,详细代码事例如下

多敏策略

 /**
  * 脱敏策略
  */
 public enum SecretStrategy {
 ​
     /**
      * 用户名脱敏
      */
     NAME(str -> str.replaceAll(".(?=.)", "*")),
 ​
     /**
      * 身份证脱敏
      */
     ID_CARD(str -> str.replaceAll(".(?=.{4})", "*")),
 ​
     /**
      * 手机号脱敏
      */
     PHONE(str -> str.replaceAll("(\d{3})\d{4}(\d{4})", "$1****$2"));
 ​
     private final Function<String, String> desensitize;
 ​
     public Function<String, String> getDesensitize() {
         return desensitize;
     }
 ​
     SecretStrategy(Function<String, String> desensitize) {
         this.desensitize = desensitize;
     }
 }

字段注解

 @Target(ElementType.FIELD)
 @Retention(RetentionPolicy.RUNTIME)
 @JacksonAnnotationsInside
 @JsonSerialize(using = SecretJsonSerializer.class)
 public @interface ProtectColumn {
     // 脱敏策略
     SecretStrategy strategy() default SecretStrategy.NAME;
 ​
 }

序列化器的实现

 /**
  * 序列化器实现 只处理String类型的字段
  */
 public class SecretJsonSerializer extends JsonSerializer<String> implements ContextualSerializer {
 ​
     private SecretStrategy secretStrategy;
 ​
     /**
      * 步骤一
      * 方法来源于ContextualSerializer,获取属性上的注解属性,同时返回一个合适的序列化器
      */
     @Override
     public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {
         // 获取自定义注解
         ProtectColumn annotation = beanProperty.getAnnotation(ProtectColumn.class);
         // 注解不为空,且标注的字段为String
         if (Objects.nonNull(annotation) && Objects.equals(String.class, beanProperty.getType().getRawClass())) {
             this.secretStrategy = annotation.strategy();
             // 符合我们自定义情况,返回本序列化器,将顺利进入到该类中的serialize()方法中
             return this;
         }
         // 注解为空,字段不为String,寻找合适的序列化器进行处理
         return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);
     }
 ​
     /**
      * 步骤二
      * 方法来源于JsonSerializer<String>:指定返回类型为String类型,serialize()将修改后的数据返回
      */
     @Override
     public void serialize(String s, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
         // 脱敏
         if (!Objects.isNull(secretStrategy) && !isPermitted()) {
             // 定义策略不为空,并且不是管理员,返回策略处理过的字符串
             s = secretStrategy.getDesensitize().apply(s);
         }
         // 返回
         jsonGenerator.writeString(s);
 ​
     }
     /**
      * 因为有些地方需要获取不脱敏的数据的,这个可以根据需要做一个权限认证
      */
     public boolean isPermitted() {
         return false;
     }
 }

实体类使用

 @Data
 public class TestSensitive {
 ​
     @ProtectColumn(strategy = SecretStrategy.NAME)
     private String name;
 ​
     @ProtectColumn(strategy = SecretStrategy.PHONE)
     private String phone;
 ​
     private int age;
 ​
 }