经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » Spring Boot » 查看文章
SpringBoot项目中新增脱敏功能的实例代码
来源:jb51  时间:2022/12/2 10:59:06  对本文有异议

SpringBoot项目中新增脱敏功能

项目背景

目前正在开发一个SpringBoot项目,此项目有Web端和微信小程序端。web端提供给工作人员使用,微信小程序提供给群众进行预约操作。项目中有部分敏感数据需要脱敏传递给微信小程序,给与群众查看。

项目需求描述

项目中,由于使用端有两个,对于两个端的数据权限并不一样。Web端可以查看所有数据,小程序端只能查看脱敏后的数据。

需要开发一个通用脱敏功能

  • 手动进行脱敏操作
  • 支持多种对象,
  • 支持不同字段,并脱敏指定字段
  • 字段的脱敏方式多样
  • 字段的脱敏方式可自定义

项目解决方案

1. 解决方案

使用注解方式,来支持对指定字段,不同字段,多种脱敏操作,并可以脱离对象。
使用工具对象,通过泛型传参,来支持对不同对象的脱敏操作。

2. 实现代码

2.1 注解 Sensitive

  1. import java.lang.annotation.ElementType;
  2. import java.lang.annotation.Retention;
  3. import java.lang.annotation.RetentionPolicy;
  4. import java.lang.annotation.Target;
  5.  
  6. /**
  7. * 自定义数据脱敏
  8. *
  9. * 例如: 身份证,手机号等信息进行模糊处理
  10. *
  11. * @author lzddddd
  12. */
  13. @Retention(RetentionPolicy.RUNTIME)
  14. @Target(ElementType.FIELD)
  15. public @interface Sensitive {
  16.  
  17. /**
  18. * 脱敏数据类型
  19. */
  20. SensitiveType type() default SensitiveType.CUSTOMER;
  21.  
  22. /**
  23. * 前置不需要打码的长度
  24. */
  25. int prefixNoMaskLen() default 0;
  26.  
  27. /**
  28. * 后置不需要打码的长度
  29. */
  30. int suffixNoMaskLen() default 0;
  31.  
  32. /**
  33. * 用什么打码
  34. */
  35. String symbol() default "*";
  36.  
  37. }

2.1 脱敏类型枚举 SensitiveType

  1. public enum SensitiveType {
  2.  
  3. /**
  4. * 自定义
  5. */
  6. CUSTOMER,
  7. /**
  8. * 名称
  9. **/
  10. CHINESE_NAME,
  11. /**
  12. * 身份证证件号
  13. **/
  14. ID_CARD_NUM,
  15. /**
  16. * 手机号
  17. **/
  18. MOBILE_PHONE,
  19. /**
  20. * 固定电话
  21. */
  22. FIXED_PHONE,
  23. /**
  24. * 密码
  25. **/
  26. PASSWORD,
  27. /**
  28. * 银行卡号
  29. */
  30. BANKCARD,
  31. /**
  32. * 邮箱
  33. */
  34. EMAIL,
  35. /**
  36. * 地址
  37. */
  38. ADDRESS,
  39.  
  40. }

2.3 脱敏工具 DesensitizedUtils

  1. import com.ruoyi.common.annotation.Sensitive;
  2. import com.ruoyi.common.constant.HttpStatus;
  3. import com.ruoyi.common.core.domain.AjaxResult;
  4. import com.ruoyi.common.enums.SensitiveType;
  5. import lombok.extern.slf4j.Slf4j;
  6.  
  7. import java.lang.reflect.Field;
  8. import java.util.*;
  9.  
  10. @Slf4j
  11. public class DesensitizedUtils<T> {
  12.  
  13. /**
  14. * 脱敏数据列表
  15. */
  16. private List<T> list;
  17.  
  18. /**
  19. * 注解列表
  20. */
  21. private List<Object[]> fields;
  22.  
  23. /**
  24. * 实体对象
  25. */
  26. public Class<T> clazz;
  27.  
  28.  
  29. public DesensitizedUtils(Class<T> clazz)
  30. {
  31. this.clazz = clazz;
  32. }
  33.  
  34. /**
  35. * 初始化数据
  36. *
  37. * @param list 需要处理数据
  38. */
  39. public void init(List<T> list){
  40. if (list == null)
  41. {
  42. list = new ArrayList<T>();
  43. }
  44. this.list = list;
  45.  
  46. // 得到所有定义字段
  47. createSensitiveField();
  48. }
  49.  
  50. /**
  51. * 初始化数据
  52. *
  53. * @param t 需要处理数据
  54. */
  55. public void init(T t){
  56.  
  57. list = new ArrayList<T>();
  58.  
  59. if (t != null)
  60. {
  61. list.add(t);
  62. }
  63.  
  64. // 得到所有定义字段
  65. createSensitiveField();
  66. }
  67.  
  68. /**
  69. * 得到所有定义字段
  70. */
  71. private void createSensitiveField()
  72. {
  73. this.fields = new ArrayList<Object[]>();
  74. List<Field> tempFields = new ArrayList<>();
  75. tempFields.addAll(Arrays.asList(clazz.getSuperclass().getDeclaredFields()));
  76. tempFields.addAll(Arrays.asList(clazz.getDeclaredFields()));
  77. for (Field field : tempFields)
  78. {
  79. // 单注解
  80. if (field.isAnnotationPresent(Sensitive.class))
  81. {
  82. putToField(field, field.getAnnotation(Sensitive.class));
  83. }
  84.  
  85. // 多注解
  86. // if (field.isAnnotationPresent(Excels.class))
  87. // {
  88. // Excels attrs = field.getAnnotation(Excels.class);
  89. // Excel[] excels = attrs.value();
  90. // for (Excel excel : excels)
  91. // {
  92. // putToField(field, excel);
  93. // }
  94. // }
  95. }
  96. }
  97.  
  98. /**
  99. * 对list数据源将其里面的数据进行脱敏处理
  100. *
  101. * @param list
  102. * @return 结果
  103. */
  104. public AjaxResult desensitizedList(List<T> list){
  105.  
  106. if (list == null){
  107. return AjaxResult.error("脱敏数据为空");
  108. }
  109.  
  110. // 初始化数据
  111. this.init(list);
  112.  
  113. int failTimes = 0;
  114.  
  115. for (T t: this.list) {
  116. if ((Integer)desensitization(t).get("code") != HttpStatus.SUCCESS){
  117. failTimes++;
  118. }
  119. }
  120.  
  121. if (failTimes >0){
  122. return AjaxResult.error("脱敏操作中出现失败",failTimes);
  123. }
  124.  
  125. return AjaxResult.success();
  126. }
  127.  
  128. /**
  129. * 放到字段集合中
  130. */
  131. private void putToField(Field field, Sensitive attr)
  132. {
  133. if (attr != null)
  134. {
  135. this.fields.add(new Object[] { field, attr });
  136. }
  137. }
  138.  
  139. /**
  140. * 脱敏:JavaBean模式脱敏
  141. *
  142. * @param t 需要脱敏的对象
  143. * @return
  144. */
  145. public AjaxResult desensitization(T t) {
  146. if (t == null){
  147. return AjaxResult.error("脱敏数据为空");
  148. }
  149.  
  150. // 初始化数据
  151. init(t);
  152.  
  153. try {
  154. // 遍历处理需要进行 脱敏的字段
  155. for (Object[] os : fields)
  156. {
  157. Field field = (Field) os[0];
  158. Sensitive sensitive = (Sensitive) os[1];
  159. // 设置实体类私有属性可访问
  160. field.setAccessible(true);
  161. desensitizeField(sensitive,t,field);
  162. }
  163. return AjaxResult.success(t);
  164. } catch (Exception e) {
  165. e.printStackTrace();
  166. log.error("日志脱敏处理失败,回滚,详细信息:[{}]", e);
  167. return AjaxResult.error("脱敏处理失败",e);
  168. }
  169. }
  170.  
  171. /**
  172. * 对类的属性进行脱敏
  173. *
  174. * @param attr 脱敏参数
  175. * @param vo 脱敏对象
  176. * @param field 脱敏属性
  177. * @return
  178. */
  179. private void desensitizeField(Sensitive attr, T vo, Field field) throws IllegalAccessException {
  180.  
  181. if (attr == null || vo == null || field == null){
  182. return ;
  183. }
  184.  
  185. // 读取对象中的属性
  186. Object value = field.get(vo);
  187. SensitiveType sensitiveType = attr.type();
  188. int prefixNoMaskLen = attr.prefixNoMaskLen();
  189. int suffixNoMaskLen = attr.suffixNoMaskLen();
  190. String symbol = attr.symbol();
  191.  
  192. //获取属性后现在默认处理的是String类型,其他类型数据可扩展
  193. Object val = convertByType(sensitiveType, value, prefixNoMaskLen, suffixNoMaskLen, symbol);
  194. field.set(vo, val);
  195. }
  196.  
  197. /**
  198. * 以类的属性的get方法方法形式获取值
  199. *
  200. * @param o 对象
  201. * @param name 属性名
  202. * @return value
  203. * @throws Exception
  204. */
  205. private Object getValue(Object o, String name) throws Exception
  206. {
  207. if (StringUtils.isNotNull(o) && StringUtils.isNotEmpty(name))
  208. {
  209. Class<?> clazz = o.getClass();
  210. Field field = clazz.getDeclaredField(name);
  211. field.setAccessible(true);
  212. o = field.get(o);
  213. }
  214. return o;
  215. }
  216.  
  217. /**
  218. * 根据不同注解类型处理不同字段
  219. */
  220. private Object convertByType(SensitiveType sensitiveType, Object value, int prefixNoMaskLen, int suffixNoMaskLen, String symbol) {
  221. switch (sensitiveType) {
  222. case CUSTOMER:
  223. value = customer(value, prefixNoMaskLen, suffixNoMaskLen, symbol);
  224. break;
  225. case CHINESE_NAME:
  226. value = chineseName(value, symbol);
  227. break;
  228. case ID_CARD_NUM:
  229. value = idCardNum(value, symbol);
  230. break;
  231. case MOBILE_PHONE:
  232. value = mobilePhone(value, symbol);
  233. break;
  234. case FIXED_PHONE:
  235. value = fixedPhone(value, symbol);
  236. break;
  237. case PASSWORD:
  238. value = password(value, symbol);
  239. break;
  240. case BANKCARD:
  241. value = bankCard(value, symbol);
  242. break;
  243. case EMAIL:
  244. value = email(value, symbol);
  245. break;
  246. case ADDRESS:
  247. value = address(value, symbol);
  248. break;
  249. }
  250. return value;
  251. }
  252.  
  253. /*--------------------------下面的脱敏工具类也可以单独对某一个字段进行使用-------------------------*/
  254.  
  255. /**
  256. * 【自定义】 根据设置进行配置
  257. *
  258. * @param value 需处理数据
  259. * @param symbol 填充字符
  260. * @return 脱敏后数据
  261. */
  262. public Object customer(Object value, int prefixNoMaskLen, int suffixNoMaskLen, String symbol) {
  263.  
  264. //针对字符串的处理
  265. if (value instanceof String){
  266. return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol);
  267. }
  268.  
  269. return value;
  270. }
  271.  
  272. /**
  273. * 对字符串进行脱敏处理
  274. *
  275. * @param s 需处理数据
  276. * @param prefixNoMaskLen 开头展示字符长度
  277. * @param suffixNoMaskLen 结尾展示字符长度
  278. * @param symbol 填充字符
  279. * @return
  280. */
  281. private String handleString(String s, int prefixNoMaskLen, int suffixNoMaskLen, String symbol){
  282. // 是否为空
  283. if (StringUtils.isBlank(s)) {
  284. return "";
  285. }
  286.  
  287. // 如果设置为空之类使用 * 代替
  288. if (StringUtils.isBlank(symbol)){
  289. symbol = "*";
  290. }
  291.  
  292. // 对长度进行判断
  293. int length = s.length();
  294. if (length > prefixNoMaskLen + suffixNoMaskLen){
  295. String namePrefix = StringUtils.left(s, prefixNoMaskLen);
  296. String nameSuffix = StringUtils.right(s, suffixNoMaskLen);
  297. s = StringUtils.rightPad(namePrefix, StringUtils.length(s) - suffixNoMaskLen, symbol).concat(nameSuffix);
  298. }
  299.  
  300. return s;
  301. }
  302.  
  303. /**
  304. * 【中文姓名】只显示第一个汉字,其他隐藏为2个星号,比如:李**
  305. *
  306. * @param value 需处理数据
  307. * @param symbol 填充字符
  308. * @return 脱敏后数据
  309. */
  310. public String chineseName(Object value, String symbol) {
  311.  
  312. //针对字符串的处理
  313. if (value instanceof String){
  314. // 对前后长度进行设置 默认 开头只展示一个字符
  315. int prefixNoMaskLen = 1;
  316. int suffixNoMaskLen = 0;
  317.  
  318. return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol);
  319. }
  320. return "";
  321. }
  322.  
  323. /**
  324. * 【身份证号】显示最后四位,其他隐藏。共计18位或者15位,比如:*************1234
  325. *
  326. * @param value 需处理数据
  327. * @param symbol 填充字符
  328. * @return 脱敏后数据
  329. */
  330. public String idCardNum(Object value, String symbol) {
  331.  
  332. //针对字符串的处理
  333. if (value instanceof String){
  334. // 对前后长度进行设置 默认 结尾只展示四个字符
  335. int prefixNoMaskLen = 0;
  336. int suffixNoMaskLen = 4;
  337.  
  338. return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol);
  339. }
  340. return "";
  341. }
  342.  
  343. /**
  344. * 【固定电话】 显示后四位,其他隐藏,比如:*******3241
  345. *
  346. * @param value 需处理数据
  347. * @param symbol 填充字符
  348. * @return 脱敏后数据
  349. */
  350. public String fixedPhone(Object value, String symbol) {
  351.  
  352. //针对字符串的处理
  353. if (value instanceof String){
  354. // 对前后长度进行设置 默认 结尾只展示四个字符
  355. int prefixNoMaskLen = 0;
  356. int suffixNoMaskLen = 4;
  357.  
  358. return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol);
  359. }
  360. return "";
  361.  
  362. }
  363.  
  364. /**
  365. * 【手机号码】前三位,后四位,其他隐藏,比如:135****6810
  366. *
  367. * @param value 需处理数据
  368. * @param symbol 填充字符
  369. * @return 脱敏后数据
  370. */
  371. public String mobilePhone(Object value, String symbol) {
  372.  
  373. //针对字符串的处理
  374. if (value instanceof String){
  375. // 对前后长度进行设置 默认 开头只展示三个字符 结尾只展示四个字符
  376. int prefixNoMaskLen = 3;
  377. int suffixNoMaskLen = 4;
  378.  
  379. return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol);
  380. }
  381. return "";
  382. }
  383.  
  384. /**
  385. * 【地址】只显示到地区,不显示详细地址,比如:湖南省长沙市岳麓区***
  386. * 只能处理 省市区的数据
  387. *
  388. * @param value 需处理数据
  389. * @param symbol 填充字符
  390. * @return
  391. */
  392. public String address(Object value, String symbol) {
  393.  
  394. //针对字符串的处理
  395. if (value instanceof String){
  396. // 对前后长度进行设置 默认 开头只展示九个字符
  397. int prefixNoMaskLen = 9;
  398. int suffixNoMaskLen = 0;
  399.  
  400. return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol);
  401. }
  402. return "";
  403. }
  404.  
  405. /**
  406. * 【电子邮箱】 邮箱前缀仅显示第一个字母,前缀其他隐藏,用星号代替,@及后面的地址显示,比如:d**@126.com
  407. *
  408. * @param value 需处理数据
  409. * @param symbol 填充字符
  410. * @return 脱敏后数据
  411. */
  412. public String email(Object value, String symbol) {
  413.  
  414. //针对字符串的处理
  415. if (value instanceof String){
  416. // 对前后长度进行设置 默认 开头只展示一个字符 结尾只展示@及后面的地址
  417. int prefixNoMaskLen = 1;
  418. int suffixNoMaskLen = 4;
  419.  
  420. String s = (String) value;
  421. if (StringUtils.isBlank(s)) {
  422. return "";
  423. }
  424.  
  425. // 获取最后一个@
  426. int lastIndex = StringUtils.lastIndexOf(s, "@");
  427. if (lastIndex <= 1) {
  428. return s;
  429. } else {
  430. suffixNoMaskLen = s.length() - lastIndex;
  431. }
  432.  
  433. return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol);
  434. }
  435. return "";
  436.  
  437. }
  438.  
  439. /**
  440. * 【银行卡号】前六位,后四位,其他用星号隐藏每位1个星号,比如:6222600**********1234
  441. *
  442. * @param value 需处理数据
  443. * @param symbol 填充字符
  444. * @return 脱敏后数据
  445. */
  446. public String bankCard(Object value, String symbol) {
  447.  
  448. //针对字符串的处理
  449. if (value instanceof String){
  450. // 对前后长度进行设置 默认 开头只展示六个字符 结尾只展示四个字符
  451. int prefixNoMaskLen = 6;
  452. int suffixNoMaskLen = 4;
  453.  
  454. return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol);
  455. }
  456. return "";
  457.  
  458. }
  459.  
  460. /**
  461. * 【密码】密码的全部字符都用*代替,比如:******
  462. *
  463. * @param value 需处理数据
  464. * @param symbol 填充字符
  465. * @return
  466. */
  467. public String password(Object value,String symbol) {
  468.  
  469. //针对字符串的处理
  470. if (value instanceof String){
  471. // 对前后长度进行设置 默认 开头只展示六个字符 结尾只展示四个字符
  472. int prefixNoMaskLen = 0;
  473. int suffixNoMaskLen = 0;
  474.  
  475. return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol);
  476. }
  477. return "";
  478. }
  479. }

3 使用实例

3.1 需注解对象

  1. public class User {
  2. private static final long serialVersionUID = 1L;
  3.  
  4. /** 普通用户ID */
  5. private Long userId;
  6.  
  7. /** 昵称 */
  8. @Sensitive(type = SensitiveType.CUSTOMER,prefixNoMaskLen = 2,suffixNoMaskLen = 1)
  9. private String nickName;
  10.  
  11. /** 姓名 */
  12. @Sensitive(type = SensitiveType.CHINESE_NAME)
  13. private String userName;
  14.  
  15. /** 身份证 */
  16. @Sensitive(type = SensitiveType.ID_CARD_NUM)
  17. private String identityCard;
  18.  
  19. /** 手机号码 */
  20. @Sensitive(type = SensitiveType.MOBILE_PHONE)
  21. private String phoneNumber;
  22. }

3.2 脱敏操作

  1. // 脱敏对象
  2. User user = new User();
  3. ......
  4. DesensitizedUtils<User> desensitizedUtils = new DesensitizedUtils<>(User.class);
  5. desensitizedUtils.desensitization(user);
  6.  
  7. //脱敏队列
  8. List<User> users = new ArrayList<>();
  9. ......
  10. DesensitizedUtils<User> desensitizedUtils = new DesensitizedUtils<>(User.class);
  11. desensitizedUtils.desensitizedList(users);

到此这篇关于SpringBoot项目中新增脱敏功能的文章就介绍到这了,更多相关SpringBoot脱敏内容请搜索w3xue以前的文章或继续浏览下面的相关文章希望大家以后多多支持w3xue!

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号