前言

什么是数据脱敏

数据脱敏是指对某些敏感信息通过脱敏规则进行数据的变形,实现敏感隐私数据的可靠保护

常用脱敏规则

替换、重排、加密、截断、掩码

良好的数据脱敏实施

1、尽可能地为脱敏后的应用,保留脱敏前的有意义信息
2、最大程度地防止黑客进行破解

今天我们聊聊如何自定义数据脱敏

整体思路

本示例通过替换的手段实现脱敏,然后配合常用的框架特性,比如mybatis的拦截器机制或者json的序列化来快速实现脱敏

具体落地

1、定义一个脱敏工具类

可以直接引用hutool工具包,不过它在5.6+版本以上才提供了这个工具
https://www.hutool.cn/docs/#/core/工具类/信息脱敏工具-DesensitizedUtil

不然就自己实现一个,形如下

public class DesensitizedUtils {/*** 脱敏,使用默认的脱敏策略* <pre>* DesensitizedUtil.desensitized("100", DesensitizedUtils.DesensitizedType.USER_ID)) =  "0"* DesensitizedUtil.desensitized("段正淳", DesensitizedUtils.DesensitizedType.CHINESE_NAME)) = "段**"* DesensitizedUtil.desensitized("51343620000320711X", DesensitizedUtils.DesensitizedType.ID_CARD)) = "5***************1X"* DesensitizedUtil.desensitized("09157518479", DesensitizedUtils.DesensitizedType.FIXED_PHONE)) = "0915*****79"* DesensitizedUtil.desensitized("18049531999", DesensitizedUtils.DesensitizedType.MOBILE_PHONE)) = "180****1999"* DesensitizedUtil.desensitized("北京市海淀区马连洼街道289号", DesensitizedUtils.DesensitizedType.ADDRESS)) = "北京市海淀区马********"* DesensitizedUtil.desensitized("duandazhi-jack@gmail.com.cn", DesensitizedUtils.DesensitizedType.EMAIL)) = "d*************@gmail.com.cn"* DesensitizedUtil.desensitized("1234567890", DesensitizedUtils.DesensitizedType.PASSWORD)) = "**********"* DesensitizedUtil.desensitized("苏D40000", DesensitizedUtils.DesensitizedType.CAR_LICENSE)) = "苏D4***0"* DesensitizedUtil.desensitized("11011111222233333256", DesensitizedUtils.DesensitizedType.BANK_CARD)) = "1101 **** **** **** 3256"* </pre>** @param str              字符串* @param desensitizedType 脱敏类型;可以脱敏:用户id、中文名、身份证号、座机号、手机号、地址、电子邮件、密码* @return 脱敏之后的字符串* @author dazer and neusoft and qiaomu* @since 5.6.2*/public static String desensitized(CharSequence str, DesensitizedType desensitizedType) {if (StrUtil.isBlank(str)) {return StrUtil.EMPTY;}String newStr = String.valueOf(str);switch (desensitizedType) {case USER_ID:newStr = String.valueOf(DesensitizedUtils.userId());break;case CHINESE_NAME:newStr = DesensitizedUtils.chineseName(String.valueOf(str));break;case ID_CARD:newStr = DesensitizedUtils.idCardNum(String.valueOf(str), 1, 2);break;case FIXED_PHONE:newStr = DesensitizedUtils.fixedPhone(String.valueOf(str));break;case MOBILE_PHONE:newStr = DesensitizedUtils.mobilePhone(String.valueOf(str));break;case ADDRESS:newStr = DesensitizedUtils.address(String.valueOf(str), 8);break;case EMAIL:newStr = DesensitizedUtils.email(String.valueOf(str));break;case PASSWORD:newStr = DesensitizedUtils.password(String.valueOf(str));break;case CAR_LICENSE:newStr = DesensitizedUtils.carLicense(String.valueOf(str));break;case BANK_CARD:newStr = DesensitizedUtils.bankCard(String.valueOf(str));break;default:}return newStr;}/*** 【用户id】不对外提供userId** @return 脱敏后的主键*/public static Long userId() {return 0L;}/*** 【中文姓名】只显示第一个汉字,其他隐藏为2个星号,比如:李**** @param fullName 姓名* @return 脱敏后的姓名*/public static String chineseName(String fullName) {if (StrUtil.isBlank(fullName)) {return StrUtil.EMPTY;}return StrUtil.hide(fullName, 1, fullName.length());}/*** 【身份证号】前1位 和后2位** @param idCardNum 身份证* @param front     保留:前面的front位数;从1开始* @param end       保留:后面的end位数;从1开始* @return 脱敏后的身份证*/public static String idCardNum(String idCardNum, int front, int end) {//身份证不能为空if (StrUtil.isBlank(idCardNum)) {return StrUtil.EMPTY;}//需要截取的长度不能大于身份证号长度if ((front + end) > idCardNum.length()) {return StrUtil.EMPTY;}//需要截取的不能小于0if (front < 0 || end < 0) {return StrUtil.EMPTY;}return StrUtil.hide(idCardNum, front, idCardNum.length() - end);}/*** 【固定电话 前四位,后两位** @param num 固定电话* @return 脱敏后的固定电话;*/public static String fixedPhone(String num) {if (StrUtil.isBlank(num)) {return StrUtil.EMPTY;}return StrUtil.hide(num, 4, num.length() - 2);}/*** 【手机号码】前三位,后4位,其他隐藏,比如135****2210** @param num 移动电话;* @return 脱敏后的移动电话;*/public static String mobilePhone(String num) {if (StrUtil.isBlank(num)) {return StrUtil.EMPTY;}return StrUtil.hide(num, 3, num.length() - 4);}/*** 【地址】只显示到地区,不显示详细地址,比如:北京市海淀区****** @param address       家庭住址* @param sensitiveSize 敏感信息长度* @return 脱敏后的家庭地址*/public static String address(String address, int sensitiveSize) {if (StrUtil.isBlank(address)) {return StrUtil.EMPTY;}int length = address.length();return StrUtil.hide(address, length - sensitiveSize, length);}/*** 【电子邮箱】邮箱前缀仅显示第一个字母,前缀其他隐藏,用星号代替,@及后面的地址显示,比如:d**@126.com** @param email 邮箱* @return 脱敏后的邮箱*/public static String email(String email) {if (StrUtil.isBlank(email)) {return StrUtil.EMPTY;}int index = StrUtil.indexOf(email, '@');if (index <= 1) {return email;}return StrUtil.hide(email, 1, index);}/*** 【密码】密码的全部字符都用*代替,比如:******** @param password 密码* @return 脱敏后的密码*/public static String password(String password) {if (StrUtil.isBlank(password)) {return StrUtil.EMPTY;}return StrUtil.repeat('*', password.length());}/*** 【中国车牌】车牌中间用*代替* eg1:null       -》 ""* eg1:""         -》 ""* eg3:苏D40000   -》 苏D4***0* eg4:陕A12345D  -》 陕A1****D* eg5:京A123     -》 京A123     如果是错误的车牌,不处理** @param carLicense 完整的车牌号* @return 脱敏后的车牌*/public static String carLicense(String carLicense) {if (StrUtil.isBlank(carLicense)) {return StrUtil.EMPTY;}// 普通车牌if (carLicense.length() == 7) {carLicense = StrUtil.hide(carLicense, 3, 6);} else if (carLicense.length() == 8) {// 新能源车牌carLicense = StrUtil.hide(carLicense, 3, 7);}return carLicense;}/*** 银行卡号脱敏* eg: 1101 **** **** **** 3256** @param bankCardNo 银行卡号* @return 脱敏之后的银行卡号* @since 5.6.3*/public static String bankCard(String bankCardNo) {if (StrUtil.isBlank(bankCardNo)) {return bankCardNo;}bankCardNo = StrUtil.trim(bankCardNo);if (bankCardNo.length() < 9) {return bankCardNo;}final int length = bankCardNo.length();final int midLength = length - 8;final StringBuilder buf = new StringBuilder();buf.append(bankCardNo, 0, 4);for (int i = 0; i < midLength; ++i) {if (i % 4 == 0) {buf.append(CharUtil.SPACE);}buf.append('*');}buf.append(CharUtil.SPACE).append(bankCardNo, length - 4, length);return buf.toString();}
}

其实正常到这个步骤,通过替换实现脱敏就可以完成,可以直接在程序中,直接调用这个工具就行。但是作为一个懂得偷懒的程序员,肯定不满足这样。于是我们会进一步封装

2、自定义脱敏注解

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Sensitive {DesensitizedType strategy() default DesensitizedType.NONE;/*** 是否使用dfa算法* @return*/boolean useDFA() default false;/*** dfa敏感字符替换,默认替换成 "*"* @return*/String dfaReplaceChar() default "*";/*** dfa敏感字符替换次数* @return*/int dfaReplaceCharRepeatCount() default 1;}

3、利用一些框架特性提升效率

a、如果项目已经有用mybatis,则可以利用mybatis拦截器特性。实现原理就是拦截响应回来的结果,然后对结果进行脱敏处理

@Intercepts(@Signature(type = ResultSetHandler.class,method = "handleResultSets",args = Statement.class))
public class DesensitizedInterceptor implements Interceptor {@Overridepublic Object intercept(Invocation invocation) throws Throwable {List<Object> list = (List<Object>) invocation.proceed();list.forEach(EntityUtils::desensitized);return list;}}

b、 如果项目是基于springboot的web项目,则可以利用springboot自带的jackson自定义序列化实现。它的实现原来其实就是在json进行序列化渲染给前端时,进行脱敏。

如果是这种方案,则需对自定义注解进行改造一下,加上

@JacksonAnnotationsInside
@JsonSerialize(using = DesensitizedJsonSerializer.class)

注解。形如下

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@JacksonAnnotationsInside
@JsonSerialize(using = DesensitizedJsonSerializer.class)
public @interface Sensitive {DesensitizedType strategy() default DesensitizedType.NONE;/*** 是否使用dfa算法* @return*/boolean useDFA() default false;/*** dfa敏感字符替换,默认替换成 "*"* @return*/String dfaReplaceChar() default "*";/*** dfa敏感字符替换次数* @return*/int dfaReplaceCharRepeatCount() default 1;}

序列化脱敏逻辑核心代码如下

public class DesensitizedJsonSerializer extends JsonSerializer<String> implements ContextualSerializer {private Sensitive sensitive;@Overridepublic void serialize(String s, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {jsonGenerator.writeString(EntityUtils.getDesensitizedValue(sensitive,s));}@Overridepublic JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {sensitive = beanProperty.getAnnotation(Sensitive.class);if(!ObjectUtils.isEmpty(sensitive) && String.class.isAssignableFrom(beanProperty.getType().getRawClass())){return this;}return serializerProvider.findValueSerializer(beanProperty.getType(),beanProperty);}
}

示例

以json那种方式为例

1、定义实体对象,需要进行脱敏的属性上加上脱敏注解

@Data
@EqualsAndHashCode(callSuper = false)
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class UserDTO {private Integer id;private String username;@Sensitive(strategy = DesensitizedType.PASSWORD)private String password;@Sensitive(strategy = DesensitizedType.CHINESE_NAME)private String fullname;@Sensitive(strategy = DesensitizedType.MOBILE_PHONE)private String mobile;@Sensitive(strategy = DesensitizedType.EMAIL)private String email;@Sensitive(useDFA = true,dfaReplaceChar = "#",dfaReplaceCharRepeatCount = 3)private String remark;
}

2、编写一个测试controller

@RestController
@RequestMapping("/user")
public class UserController {@Autowiredprivate UserService userService;@GetMapping(value="/list")public AjaxResult listUsers(){return AjaxResult.success(userService.listUserDTO());}}

测试结果


如图所示已经进行脱敏

其他方案

1、基于Sharding Sphere实现数据脱敏

具体实现可以参考如下文章

https://jaskey.github.io/blog/2020/03/18/sharding-sphere-data-desensitization/

2、自定义注解格式化

主要实现步骤如下

  • 1、实现AnnotationFormatterFactory接口

  • 2、创建脱敏格式化类实现Formatter

  • 3、将AnnotationFormatterFactory实现的接口注册到FormatterRegistry

具体实现可以参考如下文章

https://blog.csdn.net/qq_27081015/article/details/103295983

4、利用fastjson进行脱敏

主要实现步骤如下

  • 1、实现ValueFilter接口,在process进行脱敏

  • 2、配置fastjson为默认JSON转换

/*** 配置fastjson为默认JSON转换** @return*/@Beanpublic HttpMessageConverters fastJsonHttpMessageConverters() {// 1.定义一个converters转换消息的对象FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();// 2.添加fastjson的配置信息,比如: 是否需要格式化返回的json数据FastJsonConfig fastJsonConfig = new FastJsonConfig();fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);fastJsonConfig.setSerializeFilters(new ValueDesensitizeFilter());//添加自己写的拦截器// 3.在converter中添加配置信息fastConverter.setFastJsonConfig(fastJsonConfig);// 4.将converter赋值给HttpMessageConverterHttpMessageConverter<?> converter = fastConverter;// 5.返回HttpMessageConverters对象return new HttpMessageConverters(converter);}

具体实现可以参考如下文章

https://blog.csdn.net/qq_27081015/article/details/103297316

5、利用mybatis-mate

mybatis-plus 企业(数据优雅处理)模块,使用时要配置一下授权码。如下

mybatis-mate:cert:grant: jinTianYiXueKelicense: GKXP9r4MCJhGID/DTGigcBcLmZjb1YZGjE4GXaAoxbtGsPC20sxpEtiUr2F7Nb1ANTUekvF6Syo6DzraA4M4oacwoLVTglzfvaEyUogW8L7mydqlsZ4+hlm20kK85eLJK1QsskrSJmreMnEaNh9lsV7Lpbxy9JeGCeM0HPEbRvq8Y+8dUt5bQYLklsa3ZIBexir+4XykZY15uqn1pYIp4pEK0+aINTa57xjJNoWuBIqm7BdFIb4l1TAcPYMTsMXhF5hfMmKD2h391HxWTshJ6jbt4YqdKD167AgeoM+B+DE1jxlLjcpskY+kFs9piOS7RCcmKBBUOgX2BD/JxhR2gQ==

他的实现机理就是利用json序列化那种,如果感兴趣可以参考如下链接

https://gitee.com/baomidou/mybatis-mate-examples

本文的demo也有基于mybatis-mate实现脱敏,链接如下
https://github.com/lyb-geek/springboot-learning/tree/master/springboot-desensitization/springboot-desensitzation-mybatis-mate

总结

有时候业务场景的实现方式有多种多样,大家要懂得取舍判断,比如上面的方案如果你的项目本来就没用mybatis,但为了脱敏又引入mybatis,这种方案就额外有加入了复杂度,后面维护估计就有得折腾了

demo链接

https://github.com/lyb-geek/springboot-learning/tree/master/springboot-desensitization

聊聊如何自定义数据脱敏相关推荐

  1. Java自定义数据脱敏注解

    应用场景 数据库中密文存储身份证.手机号等敏感信息时,Java需要将密文数据转换为明文并脱敏返回给前端. 脱敏方式枚举类 public enum DesensitizationTypeEnum {/* ...

  2. SpringBoot中利用自定义注解优雅地实现隐私数据脱敏(加密显示)

    前言 这两天在整改等保测出的问题,里面有一个"用户信息泄露"的风险项(就是后台系统里用户的一些隐私数据直接明文显示了),其实指的就是要做数据脱敏. 数据脱敏:把系统里的一些敏感数据 ...

  3. Java实现数据脱敏

    前言   在开发系统过程中,经常会接触到大量的数据信息,这些数据信息可能包含身份证号.手机号.姓名.卡号等各种敏感信息.而有些用户需求是不允许,这些敏感信息数据为了保护用户个人信息的安全.对这些数据需 ...

  4. 金仓数据库KingbaseES安全指南--10.数据脱敏

    目录 10.1. 数据脱敏简介 10.1.1. 关于数据脱敏 10.1.2. 使用数据脱敏的场景 10.1.3. 使用数据脱敏的优势 10.1.4. 数据脱敏的对象 10.1.5. 数据脱敏的限制 1 ...

  5. mysql的ps.setmaxrows_mysql自定义函数实现表的指定列进行数据脱敏(PS:来自mysql小白的提问)...

    要求:不考虑原来字段的值,只考虑数据长度,通过脚本update tabA set colA = stringMask(colA )实现数据脱敏,例如张三丰–ASD 提问:将指定列名作为mysql自定义 ...

  6. 【SpringBoot】66、SpringBoot使用自定义注解实现返回数据脱敏操作

    在实际项目中,对于敏感数据的保护显得十分重要,数据脱敏又称数据去隐私化或数据变形,是在给定的规则.策略下对敏感数据进行变换.修改的技术机制,能够在很大程度上解决敏感数据在非可信环境中使用的问题. 本文 ...

  7. 数据脱敏——基于Java自定义注解实现日志字段脱敏

    上文说了数据过敏主要有两个思路:第一个就是在序列化实体之前先把需要脱敏的字段进行处理,之后正常序列化:第二个就是在实体序列化的时候,对要脱敏的字段进行处理. 脱敏实现思路 这里探讨第一种方法,用基于自 ...

  8. 数据脱敏平台-大数据时代的隐私保护利器

    什么是数据脱敏 又称数据漂白.数据去隐私化或数据变形.是对核心业务数据中敏感的信息,进行变形.转换.混淆,使得对业务数据中的身份.组织等隐私敏感信息进行去除或掩盖,以保护数据能被合理.安全地利用. 数 ...

  9. 数据脱敏和加密_Apache ShardingSphere数据脱敏全解决方案详解

    解决方案详解 在了解了ShardingSphere脱敏处理流程后,即可将脱敏配置.脱敏处理流程与实际场景进行结合.所有的设计开发都是为了解决业务场景遇到的痛点.那么面对之前提到的业务场景需求,又应该如 ...

最新文章

  1. 状态机思路在程序设计中的应用
  2. SQL Server 2005 Compact Edition 程序设计与性能优化
  3. CentOS7下安装zookeeper3.4.9
  4. appium java简单实例_Appium创建一个Note的实例
  5. 多点Dmall发布系统Mini OS 宣称要五年覆盖百万门店
  6. 金融市场中的NLP——情感分析
  7. [MSDN]Design Guidelines for Developing Class Libraries with .NET Framework 4
  8. adult Income 一个人的年收入是由各种因素造成的。直观上,它受个人的教育水平、年龄、性别、职业等因素的影响。
  9. windows 用户基本查看命令
  10. JAVA超市综合管理信息区块链系统毕设论文
  11. rbw设计_同步调谐可变带通滤波器的设计
  12. 双系统安装deepin20_win10deepin15.10双系统安装教程
  13. win10 远程桌面和向日葵远控哪个好用
  14. 如何从网页获取原图片
  15. 华师大 OJ 3026
  16. 用python计算圆周率_用python计算圆周率π
  17. collection与foreach
  18. 如何利用计算机做图像,什么是图像识别?图像识别是如何实现的?
  19. 用户登录之SHA1加密
  20. c++ 的vector sort遇到栈错误

热门文章

  1. keep-alive遇到的坑
  2. Python 提示:无法初始化设备 PRN
  3. Numpy 100 exercise 笔记
  4. 【无标题】2023第十五届上海国际智慧城市、物联网、大数据博览会
  5. 关于论文排版的一些记录
  6. 什么是机器学习工程师?
  7. “机器学习工程师”到底是怎样的工作?
  8. 前端机器学习--识别人脸在脸颊上画草莓
  9. CC2530 GPIO口编程2 (上拉、下拉,三态)
  10. 任务调度框架Quartz(一) Quartz——一个强大的定时任务调度框架