Java數(shù)據(jù)脫敏實現(xiàn)的方法總結(jié)
什么是數(shù)據(jù)脫敏
數(shù)據(jù)脫敏,指的是對某些敏感信息通過脫敏規(guī)則進行數(shù)據(jù)的變形,實現(xiàn)敏感隱私數(shù)據(jù)的可靠保護。摘自百度百科數(shù)據(jù)脫敏。
對數(shù)據(jù)進行脫敏的操作一般是不可逆的。
脫敏內(nèi)容
一般來說,脫敏內(nèi)容包含但不限于各種隱私數(shù)據(jù)或商業(yè)性敏感數(shù)據(jù),如身份證號、手機、郵箱、營業(yè)執(zhí)照、銀行卡號等信息,具體要求需要根據(jù)不同公司業(yè)務而定。
脫敏場景
前端頁面內(nèi)容
我司系統(tǒng)都是前后端分離的系統(tǒng),脫敏方案都是在序列化層面來做,具體的實現(xiàn)也是基于各序列化庫,如jackson、fastjson。
Jackson實現(xiàn)
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 {
? ? //執(zhí)行脫敏
? ? ? ?String resultValue = dataMask.getStrategy().process(value,dataMask.getParams());
? ? ? ?gen.writeString(resultValue);
? }
}由于不同字段需要使用的脫敏規(guī)則是不同的,所以直接使用@JsonSerialize(contentUsing = JacksonDataMaskSerializer.class)并沒有什么意義,我們需要通過自定義Jackson的注解,來實現(xiàn)一個Serializer滿足不同脫敏工作,自定義注解如下
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@JacksonAnnotationsInside
@JsonSerialize(using = JacksonDataMaskSerializer.class)
public @interface JacksonDataMask {
?
? ?/**
? ? * 脫敏策略
? ? */
? ?DataMaskType maskType() default DataMaskType.Default;
}可以看到,使用@JacksonAnnotationsInside注解,來實現(xiàn)Jackson的自定義注解功能,這樣即可滿足不同字段的脫敏要求,使用姿勢如下:
@Data
public class DemoVo{
? ?@JacksonDataMask(maskType = DataMaskType.Phone)
private String phone;
? ?@JacksonDataMask(maskType = DataMaskType.Mail)
private String email;
}至于脫敏策略規(guī)則枚舉,非常簡單,就不寫了,無非就是不同策略對字段值的部分字符替換成特殊字符,常見的如”*“;
Fastjson實現(xiàn)
Fastjson的實現(xiàn)與Jackson類似,也是自定義序列化攔截器,讀取字段上注解,然后使用注解策略進行脫敏處理,具體實現(xiàn)略。
導出數(shù)據(jù)內(nèi)容
常見導出數(shù)據(jù)的形式為導出excel,使用的導出excel工具庫如easyexcel、easypoi等,此處以easyexcel為例。
我們同樣需要自定義一個注解,如下:
public @interface ExcelDataMask {
?
? ?/**
? ? * 脫敏策略
? ? */
? ?DataMaskType maskType() default DataMaskType.Default;
}看起來是不是與前面介紹序列化庫時自定義的注解一樣,其實直接使用前面的也沒問題,本質(zhì)上是標志該字段的數(shù)據(jù)需要脫敏,以便不同實現(xiàn)的代碼可以識別。
有了自定義注解后,按照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)) {
? ? ? ? ? ? ? ? //數(shù)據(jù)脫敏后重新寫入
? ? ? ? ? ? ? ? ? ?cellData.setStringValue(dataMaskType.process(stringValue));
? ? ? ? ? ? ? }
? ? ? ? ? }
? ? ? }).sheet("模板").doWrite(list);至此,一個簡單的excel導出內(nèi)容脫敏注解就完成了。
系統(tǒng)日志內(nèi)容
在有嚴格安全規(guī)范要求公司,系統(tǒng)運行時打印的日志內(nèi)容也是需要脫敏的。
常見的日志框架無非是logback、log4j這些(slf4j只是一個門面,不提供具體日志實現(xiàn)),基本上使用方法最終都是一句log.xx來實現(xiàn)打印。此處簡單以打印json字符串為例
log.info("內(nèi)容:{}",JSON.toJsonString(dto));
一般來說,有兩種方案。
方案一(不推薦)
自定義dto轉(zhuǎn)json字符串的方案,使用json序列化攔截器進行脫敏,這種類似方案,比較知名的實現(xiàn)如唯品會脫敏方案。
該方案有明顯的缺點,即需對分散在代碼中的所有l(wèi)og打印進行改造,工作量大,并且容易遺漏。
方案二
該方案是將脫敏邏輯,與業(yè)務代碼剝離開,在日志框架層面進行實現(xiàn)。以logback為例,可以從以下兩個擴展點進行實現(xiàn)。
自定義PatternLayout
在使用logback時,一般會自定義日志輸出內(nèi)容格式,使用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);
? }
}存在的問題
- 脫敏處理的對象是字符串,脫敏內(nèi)容的識別需要使用多種正則匹配;
- 無論是否有入?yún)?、是否有敏感字段,所有日志?nèi)容都需要執(zhí)行多種正則匹配;
可以看到,自定義PatternLayout的性能相對來說是比較低的,所以實際項目上并不推薦該方案。
自定義Converter(推薦)
自定義PatternLayout是對格式化后的字符串進行脫敏,可拓展性較差。實際項目中,為了識別不同的日志信息后脫敏,更多的是自定義日志格式轉(zhuǎn)換器Converter來實現(xiàn)脫敏。簡單看下如何使用
//ClassicConverter是一個抽象類,是Converter的子類
public class DataMaskingConverter extends ClassicConverter {
? ?@Override
? ?public String convert(ILoggingEvent event) {
? ? ? ?if (event == null) {
? ? ? ? ? ?return null;
? ? ? }
? ? //log參數(shù)脫敏
? ? Object[] maskArgs = argsToMask(event.getArgumentArray());
? ? //參數(shù)脫敏后參與格式化
? ? 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 {
? ? ? ? ? ? ? ? ?//其他基礎數(shù)據(jù)類型
? ? ? ? ? ? ? ? ?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可以對入?yún)⒌念愋蛠磉x擇不同的脫敏操作,相對PatternLayout來說,減少大量正則匹配,大幅提高性能。此時log.info("內(nèi)容:{}",JSON.toJsonString(dto)) 需要改寫成log.info("內(nèi)容:{}",dto)。
但自定義Converter也存在一些問題
- 對于入?yún)⑹亲址娜罩荆鏻og.info("xx",JSON.toJsonString(dto))、log.info("xx",dto.toString()),如果字符串中包含敏感字段,想要識別,只能通過多種正則進行匹配;
- 若直接使用log.info()方法沒有參數(shù),直接打印字符串的話,如果字符串中包含敏感字段,且需要進行脫敏處理,則自定義Converter也將退化成類似前面自定義PatternLayout,只能使用正則匹配的方法實現(xiàn)脫敏。
針對自定義Converter存在的問題,在實際項目中可以發(fā)現(xiàn),如果想要單獨依賴自定義Converter完全解決日志脫敏的問題,是非常困難的,因此有以下建議
- log的方法使用時,盡量帶上參數(shù);
- 盡量避免入?yún)镾tring;
到此這篇關于Java數(shù)據(jù)脫敏實現(xiàn)的方法總結(jié)的文章就介紹到這了,更多相關Java數(shù)據(jù)脫敏內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
解決idea update project 更新選項消失的問題
這篇文章主要介紹了解決idea update project 更新選項消失的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-01-01
Springmvc數(shù)據(jù)格式化原理及代碼案例
這篇文章主要介紹了Springmvc數(shù)據(jù)格式化原理及代碼案例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-10-10
使用ShardingJDBC進行數(shù)據(jù)分片以及讀寫分離
spring boot--從controller到DAO操作

