Java數(shù)據(jù)脫敏實(shí)現(xiàn)的方法總結(jié)
什么是數(shù)據(jù)脫敏
數(shù)據(jù)脫敏,指的是對某些敏感信息通過脫敏規(guī)則進(jìn)行數(shù)據(jù)的變形,實(shí)現(xiàn)敏感隱私數(shù)據(jù)的可靠保護(hù)。摘自百度百科數(shù)據(jù)脫敏。
對數(shù)據(jù)進(jìn)行脫敏的操作一般是不可逆的。
脫敏內(nèi)容
一般來說,脫敏內(nèi)容包含但不限于各種隱私數(shù)據(jù)或商業(yè)性敏感數(shù)據(jù),如身份證號、手機(jī)、郵箱、營業(yè)執(zhí)照、銀行卡號等信息,具體要求需要根據(jù)不同公司業(yè)務(wù)而定。
脫敏場景
前端頁面內(nèi)容
我司系統(tǒng)都是前后端分離的系統(tǒng),脫敏方案都是在序列化層面來做,具體的實(shí)現(xiàn)也是基于各序列化庫,如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)并沒有什么意義,我們需要通過自定義Jackson的注解,來實(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注解,來實(shí)現(xiàn)Jackson的自定義注解功能,這樣即可滿足不同字段的脫敏要求,使用姿勢如下:
@Data
public class DemoVo{
? ?@JacksonDataMask(maskType = DataMaskType.Phone)
private String phone;
? ?@JacksonDataMask(maskType = DataMaskType.Mail)
private String email;
}至于脫敏策略規(guī)則枚舉,非常簡單,就不寫了,無非就是不同策略對字段值的部分字符替換成特殊字符,常見的如”*“;
Fastjson實(shí)現(xiàn)
Fastjson的實(shí)現(xiàn)與Jackson類似,也是自定義序列化攔截器,讀取字段上注解,然后使用注解策略進(jìn)行脫敏處理,具體實(shí)現(xiàn)略。
導(dǎo)出數(shù)據(jù)內(nèi)容
常見導(dǎo)出數(shù)據(jù)的形式為導(dǎo)出excel,使用的導(dǎo)出excel工具庫如easyexcel、easypoi等,此處以easyexcel為例。
我們同樣需要自定義一個(gè)注解,如下:
public @interface ExcelDataMask {
?
? ?/**
? ? * 脫敏策略
? ? */
? ?DataMaskType maskType() default DataMaskType.Default;
}看起來是不是與前面介紹序列化庫時(shí)自定義的注解一樣,其實(shí)直接使用前面的也沒問題,本質(zhì)上是標(biāo)志該字段的數(shù)據(jù)需要脫敏,以便不同實(shí)現(xiàn)的代碼可以識別。
有了自定義注解后,按照Excel官方demo,并在DTO字段上進(jìn)行注解。
@Data
@AllArgsConstructor
@NoArgsConstructor
public class DemoItemDto {
?
? ?@ExcelProperty("手機(jī)號碼")
? ?@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);至此,一個(gè)簡單的excel導(dǎo)出內(nèi)容脫敏注解就完成了。
系統(tǒng)日志內(nèi)容
在有嚴(yán)格安全規(guī)范要求公司,系統(tǒng)運(yùn)行時(shí)打印的日志內(nèi)容也是需要脫敏的。
常見的日志框架無非是logback、log4j這些(slf4j只是一個(gè)門面,不提供具體日志實(shí)現(xiàn)),基本上使用方法最終都是一句log.xx來實(shí)現(xiàn)打印。此處簡單以打印json字符串為例
log.info("內(nèi)容:{}",JSON.toJsonString(dto));
一般來說,有兩種方案。
方案一(不推薦)
自定義dto轉(zhuǎn)json字符串的方案,使用json序列化攔截器進(jìn)行脫敏,這種類似方案,比較知名的實(shí)現(xiàn)如唯品會(huì)脫敏方案。
該方案有明顯的缺點(diǎn),即需對分散在代碼中的所有l(wèi)og打印進(jìn)行改造,工作量大,并且容易遺漏。
方案二
該方案是將脫敏邏輯,與業(yè)務(wù)代碼剝離開,在日志框架層面進(jìn)行實(shí)現(xiàn)。以logback為例,可以從以下兩個(gè)擴(kuò)展點(diǎn)進(jìn)行實(shí)現(xiàn)。
自定義PatternLayout
在使用logback時(shí),一般會(huì)自定義日志輸出內(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進(jìn)行脫敏
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的性能相對來說是比較低的,所以實(shí)際項(xiàng)目上并不推薦該方案。
自定義Converter(推薦)
自定義PatternLayout是對格式化后的字符串進(jìn)行脫敏,可拓展性較差。實(shí)際項(xiàng)目中,為了識別不同的日志信息后脫敏,更多的是自定義日志格式轉(zhuǎn)換器Converter來實(shí)現(xiàn)脫敏。簡單看下如何使用
//ClassicConverter是一個(gè)抽象類,是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 {
? ? ? ? ? ? ? ? ?//其他基礎(chǔ)數(shù)據(jù)類型
? ? ? ? ? ? ? ? ?res[i] = arg;
? ? ? ? ? ? ? }
? ? ? ? ? ? continue;
? ? ? ? ? }
? ? ? ? if {
? ? ? ? ? ? //其他對象
? ? ? ? ? ? ? ?res[i] = DataMask.toJSONString(arg);
? ? ? ? ? }
? ? ? ? ? ?i++;
? ? ? }
? ? ? ?return res;
? }
}在logback配置文件中,新增配置
<configuration>
<!-- conversionWord="msg",其中msg就是對應(yīng)pattern標(biāo)簽中的msg -->
? ?<conversionRule conversionWord="msg" ?converterClass="cn.cc.DataMaskingConverter"/>
</configuration>可以看到,自定義Converter可以對入?yún)⒌念愋蛠磉x擇不同的脫敏操作,相對PatternLayout來說,減少大量正則匹配,大幅提高性能。此時(shí)log.info("內(nèi)容:{}",JSON.toJsonString(dto)) 需要改寫成log.info("內(nèi)容:{}",dto)。
但自定義Converter也存在一些問題
- 對于入?yún)⑹亲址娜罩?,如log.info("xx",JSON.toJsonString(dto))、log.info("xx",dto.toString()),如果字符串中包含敏感字段,想要識別,只能通過多種正則進(jìn)行匹配;
- 若直接使用log.info()方法沒有參數(shù),直接打印字符串的話,如果字符串中包含敏感字段,且需要進(jìn)行脫敏處理,則自定義Converter也將退化成類似前面自定義PatternLayout,只能使用正則匹配的方法實(shí)現(xiàn)脫敏。
針對自定義Converter存在的問題,在實(shí)際項(xiàng)目中可以發(fā)現(xiàn),如果想要單獨(dú)依賴自定義Converter完全解決日志脫敏的問題,是非常困難的,因此有以下建議
- log的方法使用時(shí),盡量帶上參數(shù);
- 盡量避免入?yún)镾tring;
到此這篇關(guān)于Java數(shù)據(jù)脫敏實(shí)現(xiàn)的方法總結(jié)的文章就介紹到這了,更多相關(guān)Java數(shù)據(jù)脫敏內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java多線程(單例模式,堵塞隊(duì)列,定時(shí)器)詳解
這篇文章主要介紹了java多線程的(單例模式,堵塞隊(duì)列,定時(shí)器),具有一定參考價(jià)值,加深多線程編程的理解還是很有幫助的,需要的朋友可以參考下2021-08-08
Java純代碼實(shí)現(xiàn)導(dǎo)出pdf
在項(xiàng)目開發(fā)中,產(chǎn)品的需求越來越奇葩啦,開始文件下載都是下載為excel的,做著做著需求竟然變了,要求能導(dǎo)出pdf,所以本文就來用Java實(shí)現(xiàn)導(dǎo)出pdf功能吧2023-12-12
Java中的instanceof關(guān)鍵字在Android中的用法實(shí)例詳解
instanceof是Java的一個(gè)二元操作符,和==,>,<是同一類東西。接下來通過本文給大家介紹Java中的instanceof關(guān)鍵字在Android中的用法,非常不錯(cuò),具有參考借鑒價(jià)值,感興趣的朋友一起學(xué)習(xí)吧2016-07-07
SpringBoot項(xiàng)目如何將Bean注入到普通類中
這篇文章主要介紹了SpringBoot項(xiàng)目如何將Bean注入到普通類中,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11
Java注解的Retention和RetentionPolicy實(shí)例分析
這篇文章主要介紹了Java注解的Retention和RetentionPolicy,結(jié)合實(shí)例形式分析了Java注解Retention和RetentionPolicy的基本功能及使用方法,需要的朋友可以參考下2019-09-09
java?Long類型轉(zhuǎn)為json后數(shù)據(jù)損失精度的處理方式
這篇文章主要介紹了java?Long類型轉(zhuǎn)為json后數(shù)據(jù)損失精度的處理方式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01
如何應(yīng)對spring框架的HTTP ERROR 400 Bad Request錯(cuò)
這篇文章主要介紹了如何應(yīng)對spring框架的HTTP ERROR 400 Bad Request錯(cuò)誤返回問題,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-08-08

