这两天在整改等保测出的问题,里面有一个用户信息泄露的风险项(就是后台系统里用户的一些隐私数据直接明文显示了),其实指的就是要做数据脱敏。 数据脱敏:把系统里的一些敏感数据进行加密处理后再返回,达到保护隐私作用,实现效果图如下: 其实要实现上面的效果,可能最先想到的方法是直接改每个controller接口,在返回数据前做一次加密处理,当然这个方法肯定是非常捞的。这里推荐用注解来实现,即高效又优雅,省时省力,支持扩展。 其实解决方案大体上分两种:在拿到数据时就已经脱敏了(如在mysql查询时用insert函数进行隐藏)拿到数据后在序列化的时候再进行脱敏(如用fastjson、jackson) 这里我所选用的方案是第二种,即在接口返回数据前,在序列化的时候对敏感字段值进行处理,并且选用jackson的序列化来实现(推荐)1。创建隐私数据类型枚举:PrivacyTypeEnumimportlombok。G隐私数据类型枚举GetterpublicenumPrivacyTypeEnum{自定义(此项需设置脱敏的范围)CUSTOMER,姓名NAME,身份证号IDCARD,手机号PHONE,邮箱EMAIL,}2。创建自定义隐私注解:PrivacyEncryptimportcom。fasterxml。jackson。annotation。JacksonAnnotationsIimportcom。fasterxml。jackson。databind。annotation。JsonSimportjava。lang。annotation。ElementTimportjava。lang。annotation。Rimportjava。lang。annotation。RetentionPimportjava。lang。annotation。T自定义数据脱敏注解Target(ElementType。FIELD)作用在字段上Retention(RetentionPolicy。RUNTIME)class文件中保留,运行时也保留,能通过反射读取到JacksonAnnotationsInside表示自定义自己的注解PrivacyEncryptJsonSerialize(usingPrivacySerializer。class)该注解使用序列化的方式publicinterfacePrivacyEncrypt{脱敏数据类型(没给默认值,所以使用时必须指定type)PrivacyTypeEnumtype();前置不需要打码的长度intprefixNoMaskLen()default1;后置不需要打码的长度intsuffixNoMaskLen()default1;用什么打码Stringsymbol()}3。创建自定义序列化器:PrivacySerializerimportcom。fasterxml。jackson。core。JsonGimportcom。fasterxml。jackson。databind。BeanPimportcom。fasterxml。jackson。databind。JsonMappingEimportcom。fasterxml。jackson。databind。JsonSimportcom。fasterxml。jackson。databind。SerializerPimportcom。fasterxml。jackson。databind。ser。ContextualSimportcom。zk。common。core。domain。enumerate。PrivacyTypeEimportcom。zk。common。core。utils。PrivacyUimportjava。io。IOEimportjava。util。Oimportlombok。AllArgsCimportlombok。NoArgsCimportorg。apache。commons。lang3。StringUNoArgsConstructorAllArgsConstructorpublicclassPrivacySerializerextendsJsonSerializerStringimplementsContextualSerializer{脱敏类型privatePrivacyTypeEnumprivacyTypeE前几位不脱敏privateIntegerprefixNoMaskL最后几位不脱敏privateIntegersuffixNoMaskL用什么打码privateSOverridepublicvoidserialize(finalStringorigin,finalJsonGeneratorjsonGenerator,finalSerializerProviderserializerProvider)throwsIOException{if(StringUtils。isNotBlank(origin)null!privacyTypeEnum){switch(privacyTypeEnum){caseCUSTOMER:jsonGenerator。writeString(PrivacyUtil。desValue(origin,prefixNoMaskLen,suffixNoMaskLen,symbol));caseNAME:jsonGenerator。writeString(PrivacyUtil。hideChineseName(origin));caseIDCARD:jsonGenerator。writeString(PrivacyUtil。hideIDCard(origin));casePHONE:jsonGenerator。writeString(PrivacyUtil。hidePhone(origin));caseEMAIL:jsonGenerator。writeString(PrivacyUtil。hideEmail(origin));default:thrownewIllegalArgumentException(unknownprivacytypeenumprivacyTypeEnum);}}}OverridepublicJsonS?createContextual(finalSerializerProviderserializerProvider,finalBeanPropertybeanProperty)throwsJsonMappingException{if(beanProperty!null){if(Objects。equals(beanProperty。getType()。getRawClass(),String。class)){PrivacyEncryptprivacyEncryptbeanProperty。getAnnotation(PrivacyEncrypt。class);if(privacyEncryptnull){privacyEncryptbeanProperty。getContextAnnotation(PrivacyEncrypt。class);}if(privacyEncrypt!null){returnnewPrivacySerializer(privacyEncrypt。type(),privacyEncrypt。prefixNoMaskLen(),privacyEncrypt。suffixNoMaskLen(),privacyEncrypt。symbol());}}returnserializerProvider。findValueSerializer(beanProperty。getType(),beanProperty);}returnserializerProvider。findNullValueSerializer(null);}} 这里是具体的实现过程,因为要脱敏的数据都是String类型的,所以继承JsonSerializer时的类型填String 重写的serialize方法是实现脱敏的核心,根据类型type的不同去设置序列化后的值 重写的createContextual方法就是去读取我们自定义的PrivacyEncrypt注解,打造一个上下文的环境。推荐:Java面试题4。隐私数据隐藏工具类:PrivacyUtilpublicclassPrivacyUtil{隐藏手机号中间四位publicstaticStringhidePhone(Stringphone){returnphone。replaceAll((d{3})d{4}(d{4}),12);}隐藏邮箱publicstaticStringhideEmail(Stringemail){returnemail。replaceAll((w?)(w)(w)(w。〔az〕(。〔az〕)?),134);}隐藏身份证publicstaticStringhideIDCard(StringidCard){returnidCard。replaceAll((d{4})d{10}(w{4}),12);}【中文姓名】只显示第一个汉字,其他隐藏为星号,比如:任publicstaticStringhideChineseName(StringchineseName){if(chineseNamenull){}returndesValue(chineseName,1,0,);}【身份证号】显示前4位,后2位publicstaticStringhideIdCard(StringidCard){returndesValue(idCard,4,2,);}【手机号码】前三位,后四位,其他隐藏。publicstaticStringhidePhone(Stringphone){returndesValue(phone,3,4,);}对字符串进行脱敏操作paramorigin原始字符串paramprefixNoMaskLen左侧需要保留几位明文字段paramsuffixNoMaskLen右侧需要保留几位明文字段parammaskStr用于遮罩的字符串,如return脱敏后结果publicstaticStringdesValue(Stringorigin,intprefixNoMaskLen,intsuffixNoMaskLen,StringmaskStr){if(originnull){}StringBuildersbnewStringBuilder();for(inti0,norigin。length();i){if(iprefixNoMaskLen){sb。append(origin。charAt(i));}if(i(nsuffixNoMaskLen1)){sb。append(origin。charAt(i));}sb。append(maskStr);}returnsb。toString();}publicstaticvoidmain(String〔〕args){System。out。println(hideChineseName(张三三));}} 这个工具类其实可以自己定,根据自己的业务去扩展,提一点: 在自定义注解PrivacyEncrypt里,只有type的值为PrivacyTypeEnum。CUSTOMER(自定义)时,才需要指定脱敏范围,即prefixNoMaskLen和suffixNoMaskLen的值,像邮箱、手机号这种隐藏格式都采用固定的5。注解使用 直接在需要脱敏的字段上加上注解,指定type值即可,如下:DatapublicclassPeople{privateIprivateSprivateIprivateIPrivacyEncrypt(typePrivacyTypeEnum。PHONE)隐藏手机号privateSPrivacyEncrypt(typePrivacyTypeEnum。EMAIL)隐藏邮箱privateSprivateS} 到这里,脱敏工作就已经结束了,全局使用这一个注解即可,一劳永逸,测试效果图如下: 以上代码已经过测试,并已实际使用,所以可放心使用 来源:blog。csdn。netqq36737803articledetails122366043