分享

如何对数据进行脱敏处理?

 非著名问天 2022-04-17

一、背景

实际的业务开发过程中,我们经常需要对用户的隐私数据进行脱敏处理,所谓脱敏处理其实就是将数据进行混淆隐藏,例如下图,将用户的手机号、地址等数据信息,采用*进行隐藏,以免泄露个人隐私信息。

如果需要脱敏的数据范围很小很小,甚至就是指定的字段,一般的处理方式也很简单,就是写一个隐藏方法即可实现数据脱敏。

如果是需求很少的情况下,采用这种方式实现没太大问题,好维护!

但如果是类似上面那种很多位置的数据,需要分门别类的进行脱敏处理,通过这种简单粗暴的处理,代码似乎就显得不太优雅了。

思考一下,我们可不可以在数据输出的阶段,进行统一数据脱敏处理,这样就可以省下不少体力活。

说到数据输出,很多同学可能会想到JSON序列化。是的没错,我们所熟悉的web系统,就是将数据通过json序列化之后展示给前端。

那么问题来了,如何在序列化的时候,进行数据脱敏处理呢?

废话不多说,代码直接撸上!

二、程序实践

2.1、首先添加依赖包

默认的情况下,如果当前项目已经添加了spring-web包或者spring-boot-starter-web包,因为这些jar包已经集成了jackson相关包,因此无需重复依赖。

如果当前项目没有jackson包,可以通过如下方式进行添加相关依赖包。

com.fasterxml.jackson.corejackson-core2.9.8com.fasterxml.jackson.corejackson-annotations2.9.8com.fasterxml.jackson.corejackson-databind2.9.8

2.2、编写脱敏类型枚举类,满足不同场景的处理

publicenumSensitiveEnum{/***中文名*/CHINESE_NAME,/***身份证号*/ID_CARD,/***座机号*/FIXED_PHONE,/***手机号*/MOBILE_PHONE,/***地址*/ADDRESS,/***电子邮件*/EMAIL,/***银行卡*/BANK_CARD,/***公司开户银行联号*/CNAPS_CODE}

2.3、编写脱敏注解类

importcom.fasterxml.jackson.annotation.JacksonAnnotationsInside;importcom.fasterxml.jackson.databind.annotation.JsonSerialize;importjava.lang.annotation.Retention;importjava.lang.annotation.RetentionPolicy;@Retention(RetentionPolicy.RUNTIME)@JacksonAnnotationsInside@JsonSerialize(using=SensitiveSerialize.class)public@interfaceSensitiveWrapped{/***脱敏类型*@return*/SensitiveEnumvalue;}

2.4、编写脱敏序列化类

importcom.fasterxml.jackson.core.JsonGenerator;importcom.fasterxml.jackson.databind.BeanProperty;importcom.fasterxml.jackson.databind.JsonMappingException;importcom.fasterxml.jackson.databind.JsonSerializer;importcom.fasterxml.jackson.databind.SerializerProvider;importcom.fasterxml.jackson.databind.ser.ContextualSerializer;importjava.io.IOException;importjava.util.Objects;publicclassSensitiveSerializeextendsJsonSerializerimplementsContextualSerializer{/***脱敏类型*/privateSensitiveEnumtype;@Overridepublicvoidserialize(Strings,JsonGeneratorjsonGenerator,SerializerProviderserializerProvider)throwsIOException{switch(this.type){caseCHINESE_NAME:{jsonGenerator.writeString(SensitiveInfoUtils.chineseName(s));break;}caseID_CARD:{jsonGenerator.writeString(SensitiveInfoUtils.idCardNum(s));break;}caseFIXED_PHONE:{jsonGenerator.writeString(SensitiveInfoUtils.fixedPhone(s));break;}caseMOBILE_PHONE:{jsonGenerator.writeString(SensitiveInfoUtils.mobilePhone(s));break;}caseADDRESS:{jsonGenerator.writeString(SensitiveInfoUtils.address(s,4));break;}caseEMAIL:{jsonGenerator.writeString(SensitiveInfoUtils.email(s));break;}caseBANK_CARD:{jsonGenerator.writeString(SensitiveInfoUtils.bankCard(s));break;}caseCNAPS_CODE:{jsonGenerator.writeString(SensitiveInfoUtils.cnapsCode(s));break;}}}@OverridepublicJsonSerializercreateContextual(SerializerProviderserializerProvider,BeanPropertybeanProperty)throwsJsonMappingException{//为空直接跳过if(beanProperty!=null){//非String类直接跳过if(Objects.equals(beanProperty.getType.getRawClass,String.class)){SensitiveWrappedsensitiveWrapped=beanProperty.getAnnotation(SensitiveWrapped.class);if(sensitiveWrapped==null){sensitiveWrapped=beanProperty.getContextAnnotation(SensitiveWrapped.class);}if(sensitiveWrapped!=null){//如果能得到注解,就将注解的value传入SensitiveSerializereturnnewSensitiveSerialize(sensitiveWrapped.value);}}returnserializerProvider.findValueSerializer(beanProperty.getType,beanProperty);}returnserializerProvider.findNullValueSerializer(beanProperty);}publicSensitiveSerialize{}publicSensitiveSerialize(finalSensitiveEnumtype){this.type=type;}}

其中createContextual的作用是通过字段已知的上下文信息定制JsonSerializer对象。

2.4、编写脱敏工具类

importorg.apache.commons.lang3.StringUtils;publicclassSensitiveInfoUtils{/***[中文姓名]只显示第一个汉字,其他隐藏为2个星号<例子:李**>*/publicstaticStringchineseName(finalStringfullName){if(StringUtils.isBlank(fullName)){return'';}finalStringname=StringUtils.left(fullName,1);returnStringUtils.rightPad(name,StringUtils.length(fullName),'*');}/***[中文姓名]只显示第一个汉字,其他隐藏为2个星号<例子:李**>*/publicstaticStringchineseName(finalStringfamilyName,finalStringgivenName){if(StringUtils.isBlank(familyName)||StringUtils.isBlank(givenName)){return'';}returnchineseName(familyName+givenName);}/***[身份证号]显示最后四位,其他隐藏。共计18位或者15位。<例子:420**********5762>*/publicstaticStringidCardNum(finalStringid){if(StringUtils.isBlank(id)){return'';}returnStringUtils.left(id,3).concat(StringUtils.removeStart(StringUtils.leftPad(StringUtils.right(id,4),StringUtils.length(id),'*'),'***'));}/***[固定电话]后四位,其他隐藏<例子:****1234>*/publicstaticStringfixedPhone(finalStringnum){if(StringUtils.isBlank(num)){return'';}returnStringUtils.leftPad(StringUtils.right(num,4),StringUtils.length(num),'*');}/***[手机号码]前三位,后四位,其他隐藏<例子:138******1234>*/publicstaticStringmobilePhone(finalStringnum){if(StringUtils.isBlank(num)){return'';}returnStringUtils.left(num,3).concat(StringUtils.removeStart(StringUtils.leftPad(StringUtils.right(num,4),StringUtils.length(num),'*'),'***'));}/***[地址]只显示到地区,不显示详细地址;我们要对个人信息增强保护<例子:北京市海淀区****>**@paramsensitiveSize敏感信息长度*/publicstaticStringaddress(finalStringaddress,finalintsensitiveSize){if(StringUtils.isBlank(address)){return'';}finalintlength=StringUtils.length(address);returnStringUtils.rightPad(StringUtils.left(address,length-sensitiveSize),length,'*');}/***[电子邮箱]邮箱前缀仅显示第一个字母,前缀其他隐藏,用星号代替,@及后面的地址显示<例子:g**@163.com>*/publicstaticStringemail(finalStringemail){if(StringUtils.isBlank(email)){return'';}finalintindex=StringUtils.indexOf(email,'@');if(index<=1){returnemail;}else{returnStringUtils.rightPad(StringUtils.left(email,1),index,'*').concat(StringUtils.mid(email,index,StringUtils.length(email)));}}/***[银行卡号]前六位,后四位,其他用星号隐藏每位1个星号<例子:6222600**********1234>*/publicstaticStringbankCard(finalStringcardNum){if(StringUtils.isBlank(cardNum)){return'';}returnStringUtils.left(cardNum,6).concat(StringUtils.removeStart(StringUtils.leftPad(StringUtils.right(cardNum,4),StringUtils.length(cardNum),'*'),'******'));}/***[公司开户银行联号]公司开户银行联行号,显示前两位,其他用星号隐藏,每位1个星号<例子:12********>*/publicstaticStringcnapsCode(finalStringcode){if(StringUtils.isBlank(code)){return'';}returnStringUtils.rightPad(StringUtils.left(code,2),StringUtils.length(code),'*');}}

2.5、编写测试实体类

最后,我们编写一个实体类UserEntity,看看转换后的效果如何?

publicclassUserEntity{/***用户ID*/privateLonguserId;/***用户姓名*/privateStringname;/***手机号*/@SensitiveWrapped(SensitiveEnum.MOBILE_PHONE)privateStringmobile;/***身份证号码*/@SensitiveWrapped(SensitiveEnum.ID_CARD)privateStringidCard;/***年龄*/privateStringsex;/***性别*/privateintage;//省略get、set...}

测试程序如下:

publicclassSensitiveDemo{publicstaticvoidmain(String[]args)throwsJsonProcessingException{UserEntityuserEntity=newUserEntity;userEntity.setUserId(1l);userEntity.setName('张三');userEntity.setMobile('18000000001');userEntity.setIdCard('420117200001011000008888');userEntity.setAge(20);userEntity.setSex('男');//通过jackson方式,将对象序列化成json字符串ObjectMapperobjectMapper=newObjectMapper;System.out.println(objectMapper.writeValueAsString(userEntity));}}

结果如下:

{'userId':1,'name':'张三','mobile':'180****0001','idCard':'420*****************8888','sex':'男','age':20}

很清晰的看到,转换结果成功!

如果你当前的项目是基于SpringMVC框架进行开发的,那么在对象返回的时候,框架会自动帮你采用jackson框架进行序列化。

@RequestMapping('/hello')publicUserEntityhello{UserEntityuserEntity=newUserEntity;userEntity.setUserId(1l);userEntity.setName('张三');userEntity.setMobile('18000000001');userEntity.setIdCard('420117200001011000008888');userEntity.setAge(20);userEntity.setSex('男');returnuserEntity;}

三、小结

在实际的业务场景开发中,采用注解方式进行全局数据脱敏处理,可以有效的解决敏感数据隐私泄露的问题。

本文主要从实操层面对数据脱敏处理做了简单的介绍,可能有些网友还有更好的解决方案,欢迎下方留言,后面如果遇到了好的解决办法,也会分享给大家,愿对大家有所帮助!

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约