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

使用ShardingJDBC進(jìn)行數(shù)據(jù)分片以及讀寫(xiě)分離

spring boot--從controller到DAO操作

Spring?Boot簡(jiǎn)單實(shí)現(xiàn)文件上傳功能