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

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

[[430398]]

一、背景

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

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

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

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

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

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

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

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

二、程序实践

2.1、首先添加依赖包

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

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

  1. <!--jackson依赖--> 
  2. <dependency> 
  3.     <groupId>com.fasterxml.jackson.core</groupId> 
  4.     <artifactId>jackson-core</artifactId> 
  5.     <version>2.9.8</version> 
  6. </dependency> 
  7. <dependency> 
  8.     <groupId>com.fasterxml.jackson.core</groupId> 
  9.     <artifactId>jackson-annotations</artifactId> 
  10.     <version>2.9.8</version> 
  11. </dependency> 
  12. <dependency> 
  13.     <groupId>com.fasterxml.jackson.core</groupId> 
  14.     <artifactId>jackson-databind</artifactId> 
  15.     <version>2.9.8</version> 
  16. </dependency> 

 

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

  1. public enum SensitiveEnum { 
  2.  
  3.     /** 
  4.      * 中文名 
  5.      */ 
  6.     CHINESE_NAME, 
  7.  
  8.     /** 
  9.      * 身份证号 
  10.      */ 
  11.     ID_CARD, 
  12.  
  13.     /** 
  14.      * 座机号 
  15.      */ 
  16.     FIXED_PHONE, 
  17.  
  18.     /** 
  19.      * 手机号 
  20.      */ 
  21.     MOBILE_PHONE, 
  22.  
  23.     /** 
  24.      * 地址 
  25.      */ 
  26.     ADDRESS, 
  27.  
  28.     /** 
  29.      * 电子邮件 
  30.      */ 
  31.     EMAIL, 
  32.  
  33.     /** 
  34.      * 银行卡 
  35.      */ 
  36.     BANK_CARD, 
  37.  
  38.     /** 
  39.      * 公司开户银行联号 
  40.      */ 
  41.     CNAPS_CODE 

2.3、编写脱敏注解类

  1. import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; 
  2. import com.fasterxml.jackson.databind.annotation.JsonSerialize; 
  3. import java.lang.annotation.Retention; 
  4. import java.lang.annotation.RetentionPolicy; 
  5.  
  6. @Retention(RetentionPolicy.RUNTIME) 
  7. @JacksonAnnotationsInside 
  8. @JsonSerialize(using = SensitiveSerialize.class) 
  9. public @interface SensitiveWrapped { 
  10.  
  11.     /** 
  12.      * 脱敏类型 
  13.      * @return 
  14.      */ 
  15.     SensitiveEnum value(); 

2.4、编写脱敏序列化类

  1. import com.fasterxml.jackson.core.JsonGenerator; 
  2. import com.fasterxml.jackson.databind.BeanProperty; 
  3. import com.fasterxml.jackson.databind.JsonMappingException; 
  4. import com.fasterxml.jackson.databind.JsonSerializer; 
  5. import com.fasterxml.jackson.databind.SerializerProvider; 
  6. import com.fasterxml.jackson.databind.ser.ContextualSerializer; 
  7. import java.io.IOException; 
  8. import java.util.Objects; 
  9.  
  10. public class SensitiveSerialize extends JsonSerializer<String> implements ContextualSerializer { 
  11.  
  12.     /** 
  13.      * 脱敏类型 
  14.      */ 
  15.     private SensitiveEnum type; 
  16.  
  17.      
  18.     @Override 
  19.     public void serialize(String s, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { 
  20.         switch (this.type) { 
  21.             case CHINESE_NAME: { 
  22.                 jsonGenerator.writeString(SensitiveInfoUtils.chineseName(s)); 
  23.                 break; 
  24.             } 
  25.             case ID_CARD: { 
  26.                 jsonGenerator.writeString(SensitiveInfoUtils.idCardNum(s)); 
  27.                 break; 
  28.             } 
  29.             case FIXED_PHONE: { 
  30.                 jsonGenerator.writeString(SensitiveInfoUtils.fixedPhone(s)); 
  31.                 break; 
  32.             } 
  33.             case MOBILE_PHONE: { 
  34.                 jsonGenerator.writeString(SensitiveInfoUtils.mobilePhone(s)); 
  35.                 break; 
  36.             } 
  37.             case ADDRESS: { 
  38.                 jsonGenerator.writeString(SensitiveInfoUtils.address(s, 4)); 
  39.                 break; 
  40.             } 
  41.             case EMAIL: { 
  42.                 jsonGenerator.writeString(SensitiveInfoUtils.email(s)); 
  43.                 break; 
  44.             } 
  45.             case BANK_CARD: { 
  46.                 jsonGenerator.writeString(SensitiveInfoUtils.bankCard(s)); 
  47.                 break; 
  48.             } 
  49.             case CNAPS_CODE: { 
  50.                 jsonGenerator.writeString(SensitiveInfoUtils.cnapsCode(s)); 
  51.                 break; 
  52.             } 
  53.         } 
  54.     } 
  55.  
  56.     @Override 
  57.     public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException { 
  58.         // 为空直接跳过 
  59.         if (beanProperty != null) { 
  60.             // 非 String 类直接跳过 
  61.             if (Objects.equals(beanProperty.getType().getRawClass(), String.class)) { 
  62.                 SensitiveWrapped sensitiveWrapped = beanProperty.getAnnotation(SensitiveWrapped.class); 
  63.                 if (sensitiveWrapped == null) { 
  64.                     sensitiveWrapped = beanProperty.getContextAnnotation(SensitiveWrapped.class); 
  65.                 } 
  66.                 if (sensitiveWrapped != null) { 
  67.                     // 如果能得到注解,就将注解的 value 传入 SensitiveSerialize 
  68.                     return new SensitiveSerialize(sensitiveWrapped.value()); 
  69.                 } 
  70.             } 
  71.             return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty); 
  72.         } 
  73.         return serializerProvider.findNullValueSerializer(beanProperty); 
  74.     } 
  75.  
  76.     public SensitiveSerialize() {} 
  77.  
  78.     public SensitiveSerialize(final SensitiveEnum type) { 
  79.         this.type = type; 
  80.     } 

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

2.5、编写脱敏工具类

  1. import org.apache.commons.lang3.StringUtils; 
  2.  
  3. public class SensitiveInfoUtils { 
  4.  
  5.     /** 
  6.      * [中文姓名] 只显示第一个汉字,其他隐藏为2个星号<例子:李**> 
  7.      */ 
  8.     public static String chineseName(final String fullName) { 
  9.         if (StringUtils.isBlank(fullName)) { 
  10.             return ""
  11.         } 
  12.         final String name = StringUtils.left(fullName, 1); 
  13.         return StringUtils.rightPad(name, StringUtils.length(fullName), "*"); 
  14.     } 
  15.  
  16.     /** 
  17.      * [中文姓名] 只显示第一个汉字,其他隐藏为2个星号<例子:李**> 
  18.      */ 
  19.     public static String chineseName(final String familyName, final String givenName) { 
  20.         if (StringUtils.isBlank(familyName) || StringUtils.isBlank(givenName)) { 
  21.             return ""
  22.         } 
  23.         return chineseName(familyName + givenName); 
  24.     } 
  25.  
  26.     /** 
  27.      * [身份证号] 显示最后四位,其他隐藏。共计18位或者15位。<例子:420**********5762> 
  28.      */ 
  29.     public static String idCardNum(final String id) { 
  30.         if (StringUtils.isBlank(id)) { 
  31.             return ""
  32.         } 
  33.  
  34.         return StringUtils.left(id, 3).concat(StringUtils 
  35.                 .removeStart(StringUtils.leftPad(StringUtils.right(id, 4), StringUtils.length(id), "*"), 
  36.                         "***")); 
  37.     } 
  38.  
  39.     /** 
  40.      * [固定电话] 后四位,其他隐藏<例子:****1234> 
  41.      */ 
  42.     public static String fixedPhone(final String num) { 
  43.         if (StringUtils.isBlank(num)) { 
  44.             return ""
  45.         } 
  46.         return StringUtils.leftPad(StringUtils.right(num, 4), StringUtils.length(num), "*"); 
  47.     } 
  48.  
  49.     /** 
  50.      * [手机号码] 前三位,后四位,其他隐藏<例子:138******1234> 
  51.      */ 
  52.     public static String mobilePhone(final String num) { 
  53.         if (StringUtils.isBlank(num)) { 
  54.             return ""
  55.         } 
  56.         return StringUtils.left(num, 3).concat(StringUtils 
  57.                 .removeStart(StringUtils.leftPad(StringUtils.right(num, 4), StringUtils.length(num), "*"), 
  58.                         "***")); 
  59.  
  60.     } 
  61.  
  62.     /** 
  63.      * [地址] 只显示到地区,不显示详细地址;我们要对个人信息增强保护<例子:北京市海淀区****> 
  64.      * 
  65.      * @param sensitiveSize 敏感信息长度 
  66.      */ 
  67.     public static String address(final String address, final int sensitiveSize) { 
  68.         if (StringUtils.isBlank(address)) { 
  69.             return ""
  70.         } 
  71.         final int length = StringUtils.length(address); 
  72.         return StringUtils.rightPad(StringUtils.left(address, length - sensitiveSize), length, "*"); 
  73.     } 
  74.  
  75.     /** 
  76.      * [电子邮箱] 邮箱前缀仅显示第一个字母,前缀其他隐藏,用星号代替,@及后面的地址显示<例子:g**@163.com> 
  77.      */ 
  78.     public static String email(final String email) { 
  79.         if (StringUtils.isBlank(email)) { 
  80.             return ""
  81.         } 
  82.         final int index = StringUtils.indexOf(email, "@"); 
  83.         if (index <= 1) { 
  84.             return email; 
  85.         } else { 
  86.             return StringUtils.rightPad(StringUtils.left(email, 1), index"*"
  87.                     .concat(StringUtils.mid(email, index, StringUtils.length(email))); 
  88.         } 
  89.     } 
  90.  
  91.     /** 
  92.      * [银行卡号] 前六位,后四位,其他用星号隐藏每位1个星号<例子:6222600**********1234> 
  93.      */ 
  94.     public static String bankCard(final String cardNum) { 
  95.         if (StringUtils.isBlank(cardNum)) { 
  96.             return ""
  97.         } 
  98.         return StringUtils.left(cardNum, 6).concat(StringUtils.removeStart( 
  99.                 StringUtils.leftPad(StringUtils.right(cardNum, 4), StringUtils.length(cardNum), "*"), 
  100.                 "******")); 
  101.     } 
  102.  
  103.     /** 
  104.      * [公司开户银行联号] 公司开户银行联行号,显示前两位,其他用星号隐藏,每位1个星号<例子:12********> 
  105.      */ 
  106.     public static String cnapsCode(final String code) { 
  107.         if (StringUtils.isBlank(code)) { 
  108.             return ""
  109.         } 
  110.         return StringUtils.rightPad(StringUtils.left(code, 2), StringUtils.length(code), "*"); 
  111.     } 
  112.  

2.6、编写测试实体类

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

  1. public class UserEntity { 
  2.  
  3.     /** 
  4.      * 用户ID 
  5.      */ 
  6.     private Long userId; 
  7.  
  8.     /** 
  9.      * 用户姓名 
  10.      */ 
  11.     private String name
  12.  
  13.     /** 
  14.      * 手机号 
  15.      */ 
  16.     @SensitiveWrapped(SensitiveEnum.MOBILE_PHONE) 
  17.     private String mobile; 
  18.  
  19.     /** 
  20.      * 身份证号码 
  21.      */ 
  22.     @SensitiveWrapped(SensitiveEnum.ID_CARD) 
  23.     private String idCard; 
  24.  
  25.     /** 
  26.      * 年龄 
  27.      */ 
  28.     private String sex; 
  29.  
  30.     /** 
  31.      * 性别 
  32.      */ 
  33.     private int age; 
  34.  
  35.     //省略get、set... 

测试程序如下:

  1. public class SensitiveDemo { 
  2.  
  3.     public static void main(String[] args) throws JsonProcessingException { 
  4.         UserEntity userEntity = new UserEntity(); 
  5.         userEntity.setUserId(1l); 
  6.         userEntity.setName("张三"); 
  7.         userEntity.setMobile("18000000001"); 
  8.         userEntity.setIdCard("420117200001011000008888"); 
  9.         userEntity.setAge(20); 
  10.         userEntity.setSex("男"); 
  11.  
  12.         //通过jackson方式,将对象序列化成json字符串 
  13.         ObjectMapper objectMapper = new ObjectMapper(); 
  14.         System.out.println(objectMapper.writeValueAsString(userEntity)); 
  15.     } 

结果如下:

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

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

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

  1. @RequestMapping("/hello"
  2. public UserEntity hello() { 
  3.     UserEntity userEntity = new UserEntity(); 
  4.     userEntity.setUserId(1l); 
  5.     userEntity.setName("张三"); 
  6.     userEntity.setMobile("18000000001"); 
  7.     userEntity.setIdCard("420117200001011000008888"); 
  8.     userEntity.setAge(20); 
  9.     userEntity.setSex("男"); 
  10.     return userEntity; 

请求网页http://127.0.0.1:8080/hello,结果如下:

三、小结

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

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

四、参考

1、CSDN - 注解实现json序列化的时候自动进行数据脱敏

2、yanbin.blog - 自定义 Jackson 注解与禁用某一特定的注解

 

3、简书 - 数据脱敏处理

 

责任编辑:武晓燕 来源: Java极客技术
相关推荐

2021-08-09 15:00:36

SQL数据库

2018-04-25 13:32:31

数据保护GDPRCommvault

2009-05-13 09:39:00

数据中心网络设备管理

2023-10-07 08:34:27

项目API接口

2024-04-07 08:50:00

GenAIAI人工智能

2018-07-04 06:49:32

数据中心迁移服务器

2023-07-27 08:16:51

数据访问层项目

2009-09-28 09:47:55

Hibernate数据

2022-08-02 09:32:47

pandas移动计算

2023-10-10 09:13:15

Python数据的操作转换

2010-11-12 14:16:21

SQL游标

2024-02-05 13:39:00

隐私数据脱敏

2018-04-16 12:14:34

数据科学机器学习神经网络

2018-03-08 16:53:21

数据中心数据海啸

2014-04-03 13:11:07

数据中心雅虎

2009-09-01 09:45:49

Visual C#对数

2010-07-22 17:25:23

2009-07-29 17:27:23

数据中心CMDBIT

2020-07-08 15:10:11

Python数据分析代码

2024-01-22 08:46:37

MyBatis数据脱敏Spring
点赞
收藏

51CTO技术栈公众号