Java数据脱敏实现的方法总结

 更新时间:2023年07月25日 11:02:10   作者:十年培训经验的菜包  
数据脱敏,指的是对某些敏感信息通过脱敏规则进行数据的变形,实现敏感隐私数据的可靠保护,本文主要是对后端数据脱敏实现的简单总结,希望对大家有所帮助

什么是数据脱敏

数据脱敏,指的是对某些敏感信息通过脱敏规则进行数据的变形,实现敏感隐私数据的可靠保护。摘自百度百科数据脱敏。

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

脱敏内容

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

脱敏场景

前端页面内容

我司系统都是前后端分离的系统,脱敏方案都是在序列化层面来做,具体的实现也是基于各序列化库,如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);
    }
}

存在的问题

  • 脱敏处理的对象是字符串,脱敏内容的识别需要使用多种正则匹配;
  • 无论是否有入参、是否有敏感字段,所有日志内容都需要执行多种正则匹配;

可以看到,自定义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也存在一些问题

  • 对于入参是字符串的日志,如log.info("xx",JSON.toJsonString(dto))、log.info("xx",dto.toString()),如果字符串中包含敏感字段,想要识别,只能通过多种正则进行匹配;
  • 若直接使用log.info()方法没有参数,直接打印字符串的话,如果字符串中包含敏感字段,且需要进行脱敏处理,则自定义Converter也将退化成类似前面自定义PatternLayout,只能使用正则匹配的方法实现脱敏。

针对自定义Converter存在的问题,在实际项目中可以发现,如果想要单独依赖自定义Converter完全解决日志脱敏的问题,是非常困难的,因此有以下建议

  • log的方法使用时,尽量带上参数;
  • 尽量避免入参为String;

到此这篇关于Java数据脱敏实现的方法总结的文章就介绍到这了,更多相关Java数据脱敏内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Spring中propagation的7种事务配置及说明

    Spring中propagation的7种事务配置及说明

    这篇文章主要介绍了Spring中propagation的7种事务配置及说明,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-06-06
  • 解决idea update project 更新选项消失的问题

    解决idea update project 更新选项消失的问题

    这篇文章主要介绍了解决idea update project 更新选项消失的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-01-01
  • 8种常见的接口请求重试方法总结

    8种常见的接口请求重试方法总结

    在跨境业务中,可能第三方的服务器分布在世界的各个角落,所以请求三方接口的时候,难免会遇到一些网络问题,这时候需要加入重试机制了,下面小编就给大家分享几个接口重试的写法吧
    2023-11-11
  • Springmvc数据格式化原理及代码案例

    Springmvc数据格式化原理及代码案例

    这篇文章主要介绍了Springmvc数据格式化原理及代码案例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-10-10
  • Spring controller校验入参的方法详解

    Spring controller校验入参的方法详解

    项目中使用Springboot,在Controller中配置了@NotNull和@Valid,@Notnull不生效,@Valid生效,返回http status为400,本文给大家介绍了Spring controller校验入参的方法,需要的朋友可以参考下
    2024-06-06
  • 使用ShardingJDBC进行数据分片以及读写分离

    使用ShardingJDBC进行数据分片以及读写分离

    ShardingJDBC是一个轻量级的Java框架,提供了数据分片、读写分离、分布式主键生成等数据访问功能,本文将给大家介绍如何使用ShardingJDBC进行数据分片以及读写分离,需要的朋友可以参考下
    2024-01-01
  • Java线程池详细解读

    Java线程池详细解读

    这篇文章主要给大家介绍了关于Java中方法使用的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-08-08
  • spring boot--从controller到DAO操作

    spring boot--从controller到DAO操作

    这篇文章主要介绍了spring boot--从controller到DAO操作,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-06-06
  • Spring Boot简单实现文件上传功能

    Spring Boot简单实现文件上传功能

    这篇文章主要介绍了Spring Boot简单实现文件上传功能,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的小伙伴可以参考一下
    2022-08-08
  • Spring MVC数据绑定方式

    Spring MVC数据绑定方式

    这篇文章主要介绍了Spring MVC数据绑定方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-10-10

最新评论