当前位置:   article > 正文

SpringBoot实现返回值数据脱敏_springboot返回列表数据脱敏

springboot返回列表数据脱敏

介绍

  • SpringBoot实现返回数据脱敏

  • 有时,敏感数据返回时,需要进行隐藏处理,但是如果一个字段一个字段的进行硬编码处理的话,不仅增加了工作量,而且后期需求变动的时候,更加是地狱般的工作量变更。

  • 下面,通过身份证,姓名,密码,手机号等等示例去演示脱敏的流程,当然你也可以在此基础上添加自己的实现方式

原理

  • 项目使用的是SpringBoot,所以需要在序列化的时候,进行脱敏处理,springboot内置的序列化工具为jackson
  • 通过实现com.fasterxml.jackson.databind.JsonSerializer进行自定义序列化
  • 通过重写com.fasterxml.jackson.databind.ser.ContextualSerializer.createContextual获取自定义注解的信息

 实现

自定义注解类

  1. @Target(ElementType.FIELD) //作用于字段上
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @JacksonAnnotationsInside // 表示自定义自己的注解Sensitive
  4. @JsonSerialize(using = SensitiveInfoSerialize.class) // 该注解使用序列化的方式
  5. public @interface Sensitive {
  6. SensitizedType value();
  7. }

创建脱敏字段类型枚举

  1. public enum SensitizedType {
  2. /**
  3. * 用户id
  4. */
  5. USER_ID,
  6. /**
  7. * 中文名
  8. */
  9. CHINESE_NAME,
  10. /**
  11. * 身份证号
  12. */
  13. ID_CARD,
  14. /**
  15. * 座机号
  16. */
  17. FIXED_PHONE,
  18. /**
  19. * 手机号
  20. */
  21. MOBILE_PHONE,
  22. /**
  23. * 地址
  24. */
  25. ADDRESS,
  26. /**
  27. * 电子邮件
  28. */
  29. EMAIL,
  30. /**
  31. * 密码
  32. */
  33. PASSWORD,
  34. /**
  35. * 中国大陆车牌,包含普通车辆、新能源车辆
  36. */
  37. CAR_LICENSE,
  38. /**
  39. * 银行卡
  40. */
  41. BANK_CARD,
  42. /**
  43. * IPv4地址
  44. */
  45. IPV4,
  46. /**
  47. * IPv6地址
  48. */
  49. IPV6,
  50. /**
  51. * 定义了一个first_mask的规则,只显示第一个字符。
  52. */
  53. FIRST_MASK
  54. }

脱敏工具类

  1. import cn.hutool.core.util.CharUtil;
  2. import cn.hutool.core.util.StrUtil;
  3. /**
  4. * @Auther: wu
  5. * @Date: 2023/7/11
  6. * @Description: com.wu.demo.common.my_sensitive
  7. */
  8. public class SensitizedUtil {
  9. public static String desensitized(CharSequence str, SensitizedType desensitizedType) {
  10. if (StrUtil.isBlank(str)) {
  11. return StrUtil.EMPTY;
  12. }
  13. String newStr = String.valueOf(str);
  14. switch (desensitizedType) {
  15. case USER_ID:
  16. newStr = String.valueOf(userId());
  17. break;
  18. case CHINESE_NAME:
  19. newStr = chineseName(String.valueOf(str));
  20. break;
  21. case ID_CARD:
  22. newStr = idCardNum(String.valueOf(str), 3, 4);
  23. break;
  24. case FIXED_PHONE:
  25. newStr = fixedPhone(String.valueOf(str));
  26. break;
  27. case MOBILE_PHONE:
  28. newStr = mobilePhone(String.valueOf(str));
  29. break;
  30. case ADDRESS:
  31. newStr = address(String.valueOf(str), 8);
  32. break;
  33. case EMAIL:
  34. newStr = email(String.valueOf(str));
  35. break;
  36. case PASSWORD:
  37. newStr = password(String.valueOf(str));
  38. break;
  39. case CAR_LICENSE:
  40. newStr = carLicense(String.valueOf(str));
  41. break;
  42. case BANK_CARD:
  43. newStr = bankCard(String.valueOf(str));
  44. break;
  45. case IPV4:
  46. newStr = ipv4(String.valueOf(str));
  47. break;
  48. case IPV6:
  49. newStr = ipv6(String.valueOf(str));
  50. break;
  51. case FIRST_MASK:
  52. newStr = firstMask(String.valueOf(str));
  53. break;
  54. default:
  55. }
  56. return newStr;
  57. }
  58. /**
  59. * 【用户id】不对外提供userId
  60. *
  61. * @return 脱敏后的主键
  62. */
  63. public static Long userId() {
  64. return 0L;
  65. }
  66. /**
  67. * 定义了一个first_mask的规则,只显示第一个字符。<br>
  68. * 脱敏前:123456789;脱敏后:1********。
  69. *
  70. * @param str 字符串
  71. * @return 脱敏后的字符串
  72. */
  73. public static String firstMask(String str) {
  74. if (StrUtil.isBlank(str)) {
  75. return StrUtil.EMPTY;
  76. }
  77. return StrUtil.hide(str, 1, str.length());
  78. }
  79. /**
  80. * 【中文姓名】只显示第一个汉字,其他隐藏为2个星号,比如:李**
  81. *
  82. * @param fullName 姓名
  83. * @return 脱敏后的姓名
  84. */
  85. public static String chineseName(String fullName) {
  86. return firstMask(fullName);
  87. }
  88. /**
  89. * 【身份证号】前1位 和后2位
  90. *
  91. * @param idCardNum 身份证
  92. * @param front 保留:前面的front位数;从1开始
  93. * @param end 保留:后面的end位数;从1开始
  94. * @return 脱敏后的身份证
  95. */
  96. public static String idCardNum(String idCardNum, int front, int end) {
  97. //身份证不能为空
  98. if (StrUtil.isBlank(idCardNum)) {
  99. return StrUtil.EMPTY;
  100. }
  101. //需要截取的长度不能大于身份证号长度
  102. if ((front + end) > idCardNum.length()) {
  103. return StrUtil.EMPTY;
  104. }
  105. //需要截取的不能小于0
  106. if (front < 0 || end < 0) {
  107. return StrUtil.EMPTY;
  108. }
  109. return StrUtil.hide(idCardNum, front, idCardNum.length() - end);
  110. }
  111. /**
  112. * 【固定电话 前四位,后两位
  113. *
  114. * @param num 固定电话
  115. * @return 脱敏后的固定电话;
  116. */
  117. public static String fixedPhone(String num) {
  118. if (StrUtil.isBlank(num)) {
  119. return StrUtil.EMPTY;
  120. }
  121. return StrUtil.hide(num, 4, num.length() - 2);
  122. }
  123. /**
  124. * 【手机号码】前三位,后4位,其他隐藏,比如135****2210
  125. *
  126. * @param num 移动电话;
  127. * @return 脱敏后的移动电话;
  128. */
  129. public static String mobilePhone(String num) {
  130. if (StrUtil.isBlank(num)) {
  131. return StrUtil.EMPTY;
  132. }
  133. return StrUtil.hide(num, 3, num.length() - 4);
  134. }
  135. /**
  136. * 【地址】只显示到地区,不显示详细地址,比如:北京市海淀区****
  137. *
  138. * @param address 家庭住址
  139. * @param sensitiveSize 敏感信息长度
  140. * @return 脱敏后的家庭地址
  141. */
  142. public static String address(String address, int sensitiveSize) {
  143. if (StrUtil.isBlank(address)) {
  144. return StrUtil.EMPTY;
  145. }
  146. int length = address.length();
  147. return StrUtil.hide(address, length - sensitiveSize, length);
  148. }
  149. /**
  150. * 【电子邮箱】邮箱前缀仅显示第一个字母,前缀其他隐藏,用星号代替,@及后面的地址显示,比如:d**@126.com
  151. *
  152. * @param email 邮箱
  153. * @return 脱敏后的邮箱
  154. */
  155. public static String email(String email) {
  156. if (StrUtil.isBlank(email)) {
  157. return StrUtil.EMPTY;
  158. }
  159. int index = StrUtil.indexOf(email, '@');
  160. if (index <= 1) {
  161. return email;
  162. }
  163. return StrUtil.hide(email, 1, index);
  164. }
  165. /**
  166. * 【密码】密码的全部字符都用*代替,比如:******
  167. *
  168. * @param password 密码
  169. * @return 脱敏后的密码
  170. */
  171. public static String password(String password) {
  172. if (StrUtil.isBlank(password)) {
  173. return StrUtil.EMPTY;
  174. }
  175. return StrUtil.repeat('*', password.length());
  176. }
  177. /**
  178. * 【中国车牌】车牌中间用*代替
  179. * eg1:null -》 ""
  180. * eg1:"" -》 ""
  181. * eg3:苏D40000 -》 苏D4***0
  182. * eg4:陕A12345D -》 陕A1****D
  183. * eg5:京A123 -》 京A123 如果是错误的车牌,不处理
  184. *
  185. * @param carLicense 完整的车牌号
  186. * @return 脱敏后的车牌
  187. */
  188. public static String carLicense(String carLicense) {
  189. if (StrUtil.isBlank(carLicense)) {
  190. return StrUtil.EMPTY;
  191. }
  192. // 普通车牌
  193. if (carLicense.length() == 7) {
  194. carLicense = StrUtil.hide(carLicense, 3, 6);
  195. } else if (carLicense.length() == 8) {
  196. // 新能源车牌
  197. carLicense = StrUtil.hide(carLicense, 3, 7);
  198. }
  199. return carLicense;
  200. }
  201. /**
  202. * 银行卡号脱敏
  203. * eg: 1101 **** **** **** 3256
  204. *
  205. * @param bankCardNo 银行卡号
  206. * @return 脱敏之后的银行卡号
  207. * @since 5.6.3
  208. */
  209. public static String bankCard(String bankCardNo) {
  210. if (StrUtil.isBlank(bankCardNo)) {
  211. return bankCardNo;
  212. }
  213. bankCardNo = StrUtil.trim(bankCardNo);
  214. if (bankCardNo.length() < 9) {
  215. return bankCardNo;
  216. }
  217. final int length = bankCardNo.length();
  218. final int midLength = length - 8;
  219. final StringBuilder buf = new StringBuilder();
  220. buf.append(bankCardNo, 0, 4);
  221. for (int i = 0; i < midLength; ++i) {
  222. if (i % 4 == 0) {
  223. buf.append(CharUtil.SPACE);
  224. }
  225. buf.append('*');
  226. }
  227. buf.append(CharUtil.SPACE).append(bankCardNo, length - 4, length);
  228. return buf.toString();
  229. }
  230. /**
  231. * IPv4脱敏,如:脱敏前:192.0.2.1;脱敏后:192.*.*.*。
  232. *
  233. * @param ipv4 IPv4地址
  234. * @return 脱敏后的地址
  235. */
  236. public static String ipv4(String ipv4) {
  237. return StrUtil.subBefore(ipv4, '.', false) + ".*.*.*";
  238. }
  239. /**
  240. * IPv4脱敏,如:脱敏前:2001:0db8:86a3:08d3:1319:8a2e:0370:7344;脱敏后:2001:*:*:*:*:*:*:*
  241. *
  242. * @param ipv6 IPv4地址
  243. * @return 脱敏后的地址
  244. */
  245. public static String ipv6(String ipv6) {
  246. return StrUtil.subBefore(ipv6, ':', false) + ":*:*:*:*:*:*:*";
  247. }
  248. }

上述枚举类和脱敏工具类,我使用了hutool中的代码,如果hutool满足你的需求,可以直接把上述自定义注解类和自定义序列化类使用到的SensitizedType类直接替换为hutool中的cn.hutool.core.util.DesensitizedUtil.DesensitizedType的枚举类,

添加自定义序列化实现类

  1. public class SensitiveInfoSerialize extends JsonSerializer<String> implements ContextualSerializer {
  2. private SensitizedType sensitizedType;
  3. /**
  4. * 步骤一
  5. * 方法来源于ContextualSerializer,获取属性上的注解属性,同时返回一个合适的序列化器
  6. */
  7. @Override
  8. public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {
  9. // 获取自定义注解
  10. Sensitive annotation = beanProperty.getAnnotation(Sensitive.class);
  11. // 注解不为空,且标注的字段为String
  12. if(Objects.nonNull(annotation) && Objects.equals(String.class, beanProperty.getType().getRawClass())){
  13. this.sensitizedType = annotation.value();
  14. //自定义情况,返回本序列化器,将顺利进入到该类中的serialize方法中
  15. return this;
  16. }
  17. // 注解为空,字段不为String,寻找合适的序列化器进行处理
  18. return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);
  19. }
  20. /**
  21. * 步骤二
  22. * 方法来源于JsonSerializer<String>:指定返回类型为String类型,serialize()将修改后的数据返回
  23. */
  24. @Override
  25. public void serialize(String str, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
  26. if(Objects.isNull(sensitizedType)){
  27. // 定义策略为空,返回原字符串
  28. jsonGenerator.writeString(str);
  29. }else {
  30. // 定义策略不为空,返回策略处理过的字符串
  31. jsonGenerator.writeString(SensitizedUtil.desensitized(str,sensitizedType));
  32. }
  33. }
  34. }

测试验证

在需要的脱敏的实体类字段上加上相应的注解

  1. @Data
  2. public class SensitiveBody {
  3. private String name;
  4. @Sensitive(SensitizedType.MOBILE_PHONE)
  5. private String mobile;
  6. @Sensitive(SensitizedType.ID_CARD)
  7. private String idCard;
  8. }
  1. @ApiOperation(value = "脱敏测试处理")
  2. @GetMapping("sensitiveTest")
  3. public AjaxResult sensitiveTest(){
  4. SensitiveBody body = new SensitiveBody();
  5. body.setMobile("13041064026");
  6. body.setIdCard("411126189912355689");
  7. body.setName("Tom");
  8. return AjaxResult.success(body);
  9. }

本文内容由网友自发贡献,转载请注明出处:https://www.wpsshop.cn/w/酷酷是懒虫/article/detail/920886
推荐阅读
相关标签
  

闽ICP备14008679号