什么是数据脱敏

数据脱敏,指的是对某些灵敏信息经过脱敏规矩进行数据的变形,完成灵敏隐私数据的可靠保护。摘自百度百科数据脱敏。

对数据进行脱敏的操作一般是不可逆的。

脱敏内容

一般来说,脱敏内容包括但不限于各种隐私数据或商业性灵敏数据,如身份证号、手机、邮箱、营业执照、银行卡号等信息,具体要求需求根据不同公司事务而定。

脱敏场景

前端页面内容

我司体系都是前后端别离的体系,脱敏计划都是在序列化层面来做,具体的完成也是根据各序列化库,如jackson、fastjson。

Jackson完成

Jackson需求自定义一个序列化器

public class JacksonDataMaskSerializer extends StdSerializer<String> implements ContextualSerializer {
    //脱敏战略枚举
  private DataMaskType dataMask;
  
  protected JacksonDataMaskSerializer() {
    super(String.class);
   }
​
  @Override
  public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
    JacksonDataMask jacksonDataMask = property.getAnnotation(JacksonDataMask.class);
    if (jacksonDataMask != null && String.class.equals(property.getType().getRawClass())){
      dataMask = jacksonDataMask.maskType();
      return this;
     }
    return prov.findContentValueSerializer(property.getType(),property);
   }
​
  @Override
  public void serialize(String value, JsonGenerator gen, SerializerProvider provider) throws IOException {
      //履行脱敏
    String resultValue = dataMask.getStrategy().process(value,dataMask.getParams());
    gen.writeString(resultValue);
   }
}

因为不同字段需求运用的脱敏规矩是不同的,所以直接运用@JsonSerialize(contentUsing = JacksonDataMaskSerializer.class)并没有什么意义,咱们需求经过自定义Jackson的注解,来完成一个Serializer满足不同脱敏作业,自定义注解如下

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@JacksonAnnotationsInside
@JsonSerialize(using = JacksonDataMaskSerializer.class)
public @interface JacksonDataMask {
​
  /**
   * 脱敏战略
   */
  DataMaskType maskType() default DataMaskType.Default;
}

能够看到,运用@JacksonAnnotationsInside注解,来完成Jackson的自定义注解功用,这样即可满足不同字段的脱敏要求,运用姿态如下:

@Data
public class DemoVo{
 
  @JacksonDataMask(maskType = DataMaskType.Phone)
    private String phone;
 
  @JacksonDataMask(maskType = DataMaskType.Mail)
    private String email;
}

至于脱敏战略规矩枚举,十分简略,就不写了,无非便是不同战略对字段值的部分字符替换成特殊字符,常见的如”*“;

Fastjson完成

Fastjson的完成与Jackson相似,也是自定义序列化拦截器,读取字段上注解,然后运用注解战略进行脱敏处理,具体完成略。

导出数据内容

常见导出数据的方式为导出excel,运用的导出excel东西库如easyexcel、easypoi等,此处以easyexcel为例。

咱们同样需求自定义一个注解,如下:

public @interface ExcelDataMask {
​
  /**
   * 脱敏战略
   */
  DataMaskType maskType() default DataMaskType.Default;
}

看起来是不是与前面介绍序列化库时自定义的注解相同,其实直接运用前面的也没问题,本质上是标志该字段的数据需求脱敏,以便不同完成的代码能够辨认。

有了自定义注解后,依照Excel官方demo,并在DTO字段上进行注解。

@Data
@AllArgsConstructor
@NoArgsConstructor
public class DemoItemDto {
​
  @ExcelProperty("手机号码")
  @ExcelDataMask(maskType=DataMaskType.PHONE)
  private String phone;
}
EasyExcel.write("/demo.xlsx", DemoItemDto.class).registerWriteHandler(new CellWriteHandler() {
      @Override
      public void afterCellDataConverted(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, WriteCellData<?> cellData, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
        if (isHead){
          //head不需求脱敏
          return;
         }
        ExcelDataMask excelDataMask = head.getField().getAnnotation(ExcelDataMask.class);
        if (excelDataMask == null) {
          return;
         }
        DataMaskType dataMaskType = excelDataMask.maskType();
        if (dataMaskType == null) {
          return;
         }
        String stringValue = cellData.getStringValue();
        if (StrUtil.isNotBlank(stringValue)) {
            //数据脱敏后重新写入
          cellData.setStringValue(dataMaskType.process(stringValue));
         }
       }
     }).sheet("模板").doWrite(list);

至此,一个简略的excel导出内容脱敏注解就完成了。

体系日志内容

在有严厉安全规范要求公司,体系运行时打印的日志内容也是需求脱敏的。

常见的日志结构无非是logback、log4j这些(slf4j仅仅一个门面,不提供具体日志完成),基本上运用办法最终都是一句log.xx来完成打印。此处简略以打印json字符串为例

log.info(“内容:{}”,JSON.toJsonString(dto));

一般来说,有两种计划。

计划一(不引荐)

自定义dto转json字符串的计划,运用json序列化拦截器进行脱敏,这种相似计划,比较知名的完成如唯品会脱敏计划。

该计划有显着的缺点,即需对涣散在代码中的一切log打印进行改造,作业量大,而且简略遗漏。

计划二

该计划是将脱敏逻辑,与事务代码剥脱离,在日志结构层面进行完成。以logback为例,能够从以下两个扩展点进行完成。

自定义PatternLayout

在运用logback时,一般会自定义日志输出内容格局,运用PatternLayout来格局化,相似如下

<!-- CONSOLE Appender -->
  <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
    <layout class="ch.qos.logback.classic.PatternLayout">
      <pattern>
         d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
      </pattern>
    </layout>
  </appender>

直接自定义PatternLayout对msg进行脱敏

public class DataMaskingPatternLayout extends PatternLayout {
  @Override
  public String doLayout(ILoggingEvent event) {
    String msg = super.doLayout(event);
      //字符串脱敏处理
      return doMask(msg);
   }
}

存在的问题

  1. 脱敏处理的对象是字符串,脱敏内容的辨认需求运用多种正则匹配;
  2. 不管是否有入参、是否有灵敏字段,一切日志内容都需求履行多种正则匹配;

能够看到,自定义PatternLayout的功能相对来说是比较低的,所以实践项目上并不引荐该计划。

自定义Converter(引荐)

自定义PatternLayout是对格局化后的字符串进行脱敏,可拓展性较差。实践项目中,为了辨认不同的日志信息后脱敏,更多的是自定义日志格局转换器Converter来完成脱敏。简略看下如何运用

//ClassicConverter是一个抽象类,是Converter的子类
public class DataMaskingConverter extends ClassicConverter {
  @Override
  public String convert(ILoggingEvent event) {
    if (event == null) {
      return null;
     }
      //log参数脱敏
      Object[] maskArgs = argsToMask(event.getArgumentArray());
      //参数脱敏后参与格局化
      String msg = MessageFormatter.arrayFormat(event.getMessage(),maskArgs).getMessage();
    return msg;
   }
 
  @Override
  public Object[] argsToMask(Object[] argumentArray) {
    if (argumentArray == null) {
      return null;
     }
    Object[] res = new Object[argumentArray.length];
    int i = 0;
    for (Object arg : argumentArray) {
      if (arg == null || arg instanceof Throwable) {
        res[i] = arg;
        continue;
       }
        if (ObjectUtil.isBasicType(arg)) {
          if(arg instanceof String && JsonUtil.isJson(arg)){
         //json字符串
           res[i] = DataMask.maskJsonStr(arg);
         } else {
         //其他基础数据类型
         res[i] = arg;
         }
          continue;
       } 
        if {
          //其他对象
        res[i] = DataMask.toJSONString(arg);
       }
      i++;
     }
    return res;
   }
}

在logback配置文件中,新增配置

<configuration>
    <!-- conversionWord="msg",其间msg便是对应pattern标签中的msg -->
  <conversionRule conversionWord="msg" converterClass="cn.cc.DataMaskingConverter"/>
</configuration>

能够看到,自定义Converter能够对入参的类型来选择不同的脱敏操作,相对PatternLayout来说,减少大量正则匹配,大幅提高功能。此刻log.info(“内容:{}”,JSON.toJsonString(dto)) 需求改写成log.info(“内容:{}”,dto)。

但自定义Converter也存在一些问题

  1. 关于入参是字符串的日志,如log.info(“xx”,JSON.toJsonString(dto))、log.info(“xx”,dto.toString()),假如字符串中包括灵敏字段,想要辨认,只能经过多种正则进行匹配;
  2. 若直接运用log.info()办法没有参数,直接打印字符串的话,假如字符串中包括灵敏字段,且需求进行脱敏处理,则自定义Converter也将退化成相似前面自定义PatternLayout,只能运用正则匹配的办法完成脱敏。

针对自定义Converter存在的问题,在实践项目中能够发现,假如想要单独依赖自定义Converter完全处理日志脱敏的问题,是十分困难的,因此有以下建议

  1. log的办法运用时,尽量带上参数;
  2. 尽量防止入参为String;