• Java实现数据脱敏


    前言

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

    一、什么是数据脱敏

    数据脱敏又称数据去隐私化或数据变形,是在给定的规则、策略下对敏感数据进行变换、修改的技术机制,能够在很大程度上解决敏感数据在非可信环境中使用的问题。根据数据保护规范和脱敏策略.对业务数据中的敏感信息实施自动变形.实现对敏感信息的隐藏。

    二、实现

    适合单个字段

    1、枚举需要脱敏的字段类型

    1. /**
    2. * 需要脱敏的字段类型
    3. *
    4. * @author xh
    5. * @Date 2022/7/20
    6. */
    7. public enum SensitiveTypeEnum {
    8. /**
    9. * 中文名
    10. */
    11. CHINESE_NAME,
    12. /**
    13. * 身份证号
    14. */
    15. ID_CARD,
    16. /**
    17. * 座机号
    18. */
    19. FIXED_PHONE,
    20. /**
    21. * 手机号
    22. */
    23. MOBILE_PHONE,
    24. /**
    25. * 地址
    26. */
    27. ADDRESS,
    28. /**
    29. * 电子邮件
    30. */
    31. EMAIL,
    32. /**
    33. * 银行卡
    34. */
    35. BANK_CARD,
    36. /**
    37. * 虚拟账号
    38. */
    39. ACCOUNT,
    40. /**
    41. * 密码
    42. */
    43. PASSWORD;
    44. }

    2、创建注解,标识需要脱敏的字段

    1. import com.artboy.project.common.enums.SensitiveTypeEnum;
    2. import java.lang.annotation.*;
    3. /**
    4. * @author xh
    5. * @Date 2022/7/20
    6. */
    7. // 指定在注解使用的位置
    8. @Target({ElementType.FIELD, ElementType.METHOD})
    9. // 指定注解保存的范围
    10. @Retention(RetentionPolicy.RUNTIME)
    11. // 可以被子类继承
    12. @Inherited
    13. // 设置为文档说明
    14. @Documented
    15. public @interface Desensitized {
    16. /*脱敏类型(规则)*/
    17. SensitiveTypeEnum type();
    18. /*判断注解是否生效的方法*/
    19. String isEffictiveMethod() default "";
    20. }

    创建Object工具类

    用于复制对象和对对象的其他操作,注意使用fastjson实现深拷贝对于复杂的对象会出现栈溢出,这里修改为了Gson

    1. package com.artboy.project.common.utils;
    2. import com.google.gson.Gson;
    3. import org.apache.commons.lang3.StringUtils;
    4. import java.io.*;
    5. import java.lang.reflect.Array;
    6. import java.lang.reflect.Field;
    7. import java.lang.reflect.InvocationTargetException;
    8. import java.lang.reflect.Modifier;
    9. import java.util.*;
    10. /**
    11. * @author xh
    12. * @Date 2022/7/26
    13. */
    14. public class DesensitizedObjectUtils {
    15. /**
    16. * 用序列化-反序列化方式实现深克隆
    17. * 缺点:1、被拷贝的对象必须要实现序列化
    18. *
    19. * @param obj
    20. * @return
    21. */
    22. @SuppressWarnings("unchecked")
    23. public static <T> T deepCloneObject(T obj) {
    24. T t = (T) new Object();
    25. try {
    26. ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
    27. ObjectOutputStream out = new ObjectOutputStream(byteOut);
    28. out.writeObject(obj);
    29. out.close();
    30. ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());
    31. ObjectInputStream in = new ObjectInputStream(byteIn);
    32. t = (T) in.readObject();
    33. in.close();
    34. } catch (IOException e) {
    35. e.printStackTrace();
    36. } catch (ClassNotFoundException e) {
    37. e.printStackTrace();
    38. }
    39. return t;
    40. }
    41. /**
    42. * 用序列化-反序列化的方式实现深克隆
    43. * 这里使用GSON来取代FAST-JSON
    44. * 缺点:1、当实体中存在接口类型的参数,并且这个接口指向的实例为枚举类型时,会报错"com.alibaba.fastjson.JSONException: syntax error, expect {, actual string, pos 171, fieldName iLimitKey"
    45. *
    46. * @param objSource
    47. * @return
    48. */
    49. public static Object deepCloneByFastJson(Object objSource) {
    50. // String tempJson = JSON.toJSONString(objSource);
    51. // Object clone = JSON.parseObject(tempJson, objSource.getClass());
    52. Gson gson = new Gson();
    53. String tempJson = gson.toJson(objSource);
    54. Object clone = gson.fromJson(tempJson, objSource.getClass());
    55. return clone;
    56. }
    57. /**
    58. * 深度克隆对象
    59. *
    60. * @throws IllegalAccessException
    61. * @throws InstantiationException
    62. */
    63. public static Object deepClone(Object objSource) throws InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
    64. if (null == objSource) return null;
    65. //是否jdk类型、基础类型、枚举类型
    66. if (isJDKType(objSource.getClass())
    67. || objSource.getClass().isPrimitive()
    68. || objSource instanceof Enum<?>) {
    69. if ("java.lang.String".equals(objSource.getClass().getName())) {//目前只支持String类型深复制
    70. return new String((String) objSource);
    71. } else {
    72. return objSource;
    73. }
    74. }
    75. // 获取源对象类型
    76. Class<?> clazz = objSource.getClass();
    77. Object objDes = clazz.newInstance();
    78. // 获得源对象所有属性
    79. Field[] fields = getAllFields(objSource);
    80. // 循环遍历字段,获取字段对应的属性值
    81. for (Field field : fields) {
    82. field.setAccessible(true);
    83. if (null == field) continue;
    84. Object value = field.get(objSource);
    85. if (null == value) continue;
    86. Class<?> type = value.getClass();
    87. if (isStaticFinal(field)) {
    88. continue;
    89. }
    90. try {
    91. //遍历集合属性
    92. if (type.isArray()) {//对数组类型的字段进行递归过滤
    93. int len = Array.getLength(value);
    94. if (len < 1) continue;
    95. Class<?> c = value.getClass().getComponentType();
    96. Array newArray = (Array) Array.newInstance(c, len);
    97. for (int i = 0; i < len; i++) {
    98. Object arrayObject = Array.get(value, i);
    99. Array.set(newArray, i, deepClone(arrayObject));
    100. }
    101. } else if (value instanceof Collection<?>) {
    102. Collection newCollection = (Collection) value.getClass().newInstance();
    103. Collection<?> c = (Collection<?>) value;
    104. Iterator<?> it = c.iterator();
    105. while (it.hasNext()) {
    106. Object collectionObj = it.next();
    107. newCollection.add(deepClone(collectionObj));
    108. }
    109. field.set(objDes, newCollection);
    110. continue;
    111. } else if (value instanceof Map<?, ?>) {
    112. Map newMap = (Map) value.getClass().newInstance();
    113. Map<?, ?> m = (Map<?, ?>) value;
    114. Set<?> set = m.entrySet();
    115. for (Object o : set) {
    116. Map.Entry<?, ?> entry = (Map.Entry<?, ?>) o;
    117. Object mapVal = entry.getValue();
    118. newMap.put(entry.getKey(), deepClone(mapVal));
    119. }
    120. field.set(objDes, newMap);
    121. continue;
    122. }
    123. //是否jdk类型或基础类型
    124. if (isJDKType(field.get(objSource).getClass())
    125. || field.getClass().isPrimitive()
    126. || isStaticType(field)
    127. || value instanceof Enum<?>) {
    128. if ("java.lang.String".equals(value.getClass().getName())) {//目前只支持String类型深复制
    129. field.set(objDes, new String((String) value));
    130. } else {
    131. field.set(objDes, field.get(objSource));
    132. }
    133. continue;
    134. }
    135. //是否枚举
    136. if (value.getClass().isEnum()) {
    137. field.set(objDes, field.get(objSource));
    138. continue;
    139. }
    140. //是否自定义类
    141. if (isUserDefinedType(value.getClass())) {
    142. field.set(objDes, deepClone(value));
    143. continue;
    144. }
    145. } catch (Exception e) {
    146. e.printStackTrace();
    147. }
    148. }
    149. return objDes;
    150. }
    151. /**
    152. * 是否静态变量
    153. *
    154. * @param field
    155. * @return
    156. */
    157. private static boolean isStaticType(Field field) {
    158. return field.getModifiers() == 8 ? true : false;
    159. }
    160. private static boolean isStaticFinal(Field field) {
    161. return Modifier.isFinal(field.getModifiers()) && Modifier.isStatic(field.getModifiers());
    162. }
    163. /**
    164. * 是否jdk类型变量
    165. *
    166. * @param clazz
    167. * @return
    168. * @throws IllegalAccessException
    169. */
    170. private static boolean isJDKType(Class clazz) throws IllegalAccessException {
    171. //Class clazz = field.get(objSource).getClass();
    172. return org.apache.commons.lang3.StringUtils.startsWith(clazz.getPackage().getName(), "javax.")
    173. || org.apache.commons.lang3.StringUtils.startsWith(clazz.getPackage().getName(), "java.")
    174. || org.apache.commons.lang3.StringUtils.startsWith(clazz.getName(), "javax.")
    175. || org.apache.commons.lang3.StringUtils.startsWith(clazz.getName(), "java.");
    176. }
    177. /**
    178. * 是否用户自定义类型
    179. *
    180. * @param clazz
    181. * @return
    182. */
    183. private static boolean isUserDefinedType(Class<?> clazz) {
    184. return
    185. clazz.getPackage() != null
    186. && !org.apache.commons.lang3.StringUtils.startsWith(clazz.getPackage().getName(), "javax.")
    187. && !org.apache.commons.lang3.StringUtils.startsWith(clazz.getPackage().getName(), "java.")
    188. && !org.apache.commons.lang3.StringUtils.startsWith(clazz.getName(), "javax.")
    189. && !StringUtils.startsWith(clazz.getName(), "java.");
    190. }
    191. /**
    192. * 获取包括父类所有的属性
    193. *
    194. * @param objSource
    195. * @return
    196. */
    197. public static Field[] getAllFields(Object objSource) {
    198. /*获得当前类的所有属性(private、protected、public)*/
    199. List<Field> fieldList = new ArrayList<Field>();
    200. Class tempClass = objSource.getClass();
    201. while (tempClass != null && !tempClass.getName().toLowerCase().equals("java.lang.object")) {//当父类为null的时候说明到达了最上层的父类(Object类).
    202. fieldList.addAll(Arrays.asList(tempClass.getDeclaredFields()));
    203. tempClass = tempClass.getSuperclass(); //得到父类,然后赋给自己
    204. }
    205. Field[] fields = new Field[fieldList.size()];
    206. fieldList.toArray(fields);
    207. return fields;
    208. }
    209. /**
    210. * 深度克隆对象
    211. *
    212. * @throws IllegalAccessException
    213. * @throws InstantiationException
    214. */
    215. @Deprecated
    216. public static Object copy(Object objSource) throws InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
    217. if (null == objSource) return null;
    218. // 获取源对象类型
    219. Class<?> clazz = objSource.getClass();
    220. Object objDes = clazz.newInstance();
    221. // 获得源对象所有属性
    222. Field[] fields = getAllFields(objSource);
    223. // 循环遍历字段,获取字段对应的属性值
    224. for (Field field : fields) {
    225. field.setAccessible(true);
    226. // 如果该字段是 static + final 修饰
    227. if (field.getModifiers() >= 24) {
    228. continue;
    229. }
    230. try {
    231. // 设置字段可见,即可用get方法获取属性值。
    232. field.set(objDes, field.get(objSource));
    233. } catch (Exception e) {
    234. e.printStackTrace();
    235. }
    236. }
    237. return objDes;
    238. }
    239. }

    创建脱敏工具类

    1. package com.artboy.project.common.utils;
    2. import com.artboy.project.common.annotation.Desensitized;
    3. import com.google.gson.Gson;
    4. import org.apache.commons.lang3.StringUtils;
    5. import java.lang.reflect.Array;
    6. import java.lang.reflect.Field;
    7. import java.lang.reflect.InvocationTargetException;
    8. import java.lang.reflect.Method;
    9. import java.util.*;
    10. /**
    11. * @author xh
    12. * @Date 2022/7/26
    13. */
    14. public class DesensitizedUtils {
    15. /**
    16. * 获取脱敏json串(递归引用会导致java.lang.StackOverflowError)
    17. *
    18. * @param javaBean
    19. * @return
    20. */
    21. public static String getJson(Object javaBean) {
    22. String json = null;
    23. if (null != javaBean) {
    24. try {
    25. if (javaBean.getClass().isInterface()) return json;
    26. /* 克隆出一个实体进行字段修改,避免修改原实体 */
    27. //Object clone =DesensitizedObjectUtils.deepCloneObject(javaBean);
    28. //Object clone =DesensitizedObjectUtils.deepCloneByFastJson(javaBean);
    29. Object clone = DesensitizedObjectUtils.deepClone(javaBean);
    30. /* 定义一个计数器,用于避免重复循环自定义对象类型的字段 */
    31. Set<Integer> referenceCounter = new HashSet<Integer>();
    32. /* 对克隆实体进行脱敏操作 */
    33. DesensitizedUtils.replace(DesensitizedObjectUtils.getAllFields(clone), clone, referenceCounter);
    34. /* 利用fastjson对脱敏后的克隆对象进行序列化 */
    35. // json = JSON.toJSONString(clone, SerializerFeature.WriteMapNullValue, SerializerFeature.WriteNullListAsEmpty);
    36. json = new Gson().toJson(clone);
    37. /* 清空计数器 */
    38. referenceCounter.clear();
    39. referenceCounter = null;
    40. } catch (Throwable e) {
    41. e.printStackTrace();
    42. }
    43. }
    44. return json;
    45. }
    46. public static <T> T getObj(T javaBean) {
    47. T clone = null;
    48. if (null != javaBean) {
    49. try {
    50. if (javaBean.getClass().isInterface()) return null;
    51. /* 克隆出一个实体进行字段修改,避免修改原实体 */
    52. //Object clone =DesensitizedObjectUtils.deepCloneObject(javaBean);
    53. //Object clone =DesensitizedObjectUtils.deepCloneByFastJson(javaBean);
    54. clone = (T) DesensitizedObjectUtils.deepClone(javaBean);
    55. /* 定义一个计数器,用于避免重复循环自定义对象类型的字段 */
    56. Set<Integer> referenceCounter = new HashSet<Integer>();
    57. /* 对克隆实体进行脱敏操作 */
    58. DesensitizedUtils.replace(DesensitizedObjectUtils.getAllFields(clone), clone, referenceCounter);
    59. /* 清空计数器 */
    60. referenceCounter.clear();
    61. referenceCounter = null;
    62. } catch (Throwable e) {
    63. e.printStackTrace();
    64. }
    65. }
    66. return clone;
    67. }
    68. /**
    69. * 对需要脱敏的字段进行转化
    70. *
    71. * @param fields
    72. * @param javaBean
    73. * @param referenceCounter
    74. * @throws IllegalArgumentException
    75. * @throws IllegalAccessException
    76. */
    77. private static void replace(Field[] fields, Object javaBean, Set<Integer> referenceCounter) throws IllegalArgumentException, IllegalAccessException {
    78. if (null != fields && fields.length > 0) {
    79. for (Field field : fields) {
    80. field.setAccessible(true);
    81. if (null != field && null != javaBean) {
    82. Object value = field.get(javaBean);
    83. if (null != value) {
    84. Class<?> type = value.getClass();
    85. //处理子属性,包括集合中的
    86. if (type.isArray()) {//对数组类型的字段进行递归过滤
    87. int len = Array.getLength(value);
    88. for (int i = 0; i < len; i++) {
    89. Object arrayObject = Array.get(value, i);
    90. if (isNotGeneralType(arrayObject.getClass(), arrayObject, referenceCounter)) {
    91. replace(DesensitizedObjectUtils.getAllFields(arrayObject), arrayObject, referenceCounter);
    92. }
    93. }
    94. } else if (value instanceof Collection<?>) {//对集合类型的字段进行递归过滤
    95. Collection<?> c = (Collection<?>) value;
    96. Iterator<?> it = c.iterator();
    97. while (it.hasNext()) {// TODO: 待优化
    98. Object collectionObj = it.next();
    99. if (isNotGeneralType(collectionObj.getClass(), collectionObj, referenceCounter)) {
    100. replace(DesensitizedObjectUtils.getAllFields(collectionObj), collectionObj, referenceCounter);
    101. }
    102. }
    103. } else if (value instanceof Map<?, ?>) {//对Map类型的字段进行递归过滤
    104. Map<?, ?> m = (Map<?, ?>) value;
    105. Set<?> set = m.entrySet();
    106. for (Object o : set) {
    107. Map.Entry<?, ?> entry = (Map.Entry<?, ?>) o;
    108. Object mapVal = entry.getValue();
    109. if (isNotGeneralType(mapVal.getClass(), mapVal, referenceCounter)) {
    110. replace(DesensitizedObjectUtils.getAllFields(mapVal), mapVal, referenceCounter);
    111. }
    112. }
    113. } else if (value instanceof Enum<?>) {
    114. continue;
    115. }
    116. /*除基础类型、jdk类型的字段之外,对其他类型的字段进行递归过滤*/
    117. else {
    118. if (!type.isPrimitive()
    119. && type.getPackage() != null
    120. && !StringUtils.startsWith(type.getPackage().getName(), "javax.")
    121. && !StringUtils.startsWith(type.getPackage().getName(), "java.")
    122. && !StringUtils.startsWith(field.getType().getName(), "javax.")
    123. && !StringUtils.startsWith(field.getName(), "java.")
    124. && referenceCounter.add(value.hashCode())) {
    125. replace(DesensitizedObjectUtils.getAllFields(value), value, referenceCounter);
    126. }
    127. }
    128. }
    129. //脱敏操作
    130. setNewValueForField(javaBean, field, value);
    131. }
    132. }
    133. }
    134. }
    135. /**
    136. * 排除基础类型、jdk类型、枚举类型的字段
    137. *
    138. * @param clazz
    139. * @param value
    140. * @param referenceCounter
    141. * @return
    142. */
    143. private static boolean isNotGeneralType(Class<?> clazz, Object value, Set<Integer> referenceCounter) {
    144. return !clazz.isPrimitive()
    145. && clazz.getPackage() != null
    146. && !clazz.isEnum()
    147. && !StringUtils.startsWith(clazz.getPackage().getName(), "javax.")
    148. && !StringUtils.startsWith(clazz.getPackage().getName(), "java.")
    149. && !StringUtils.startsWith(clazz.getName(), "javax.")
    150. && !StringUtils.startsWith(clazz.getName(), "java.")
    151. && referenceCounter.add(value.hashCode());
    152. }
    153. /**
    154. * 脱敏操作(按照规则转化需要脱敏的字段并设置新值)
    155. * 目前只支持String类型的字段,如需要其他类型如BigDecimal、Date等类型,可以添加
    156. *
    157. * @param javaBean
    158. * @param field
    159. * @param value
    160. * @throws IllegalAccessException
    161. */
    162. public static void setNewValueForField(Object javaBean, Field field, Object value) throws IllegalAccessException {
    163. //处理自身的属性
    164. Desensitized annotation = field.getAnnotation(Desensitized.class);
    165. if (field.getType().equals(String.class) && null != annotation && executeIsEffictiveMethod(javaBean, annotation)) {
    166. String valueStr = (String) value;
    167. if (StringUtils.isNotBlank(valueStr)) {
    168. switch (annotation.type()) {
    169. case CHINESE_NAME: {
    170. field.set(javaBean, DesensitizedUtils.chineseName(valueStr));
    171. break;
    172. }
    173. case ID_CARD: {
    174. field.set(javaBean, DesensitizedUtils.idCardNum(valueStr));
    175. break;
    176. }
    177. case FIXED_PHONE: {
    178. field.set(javaBean, DesensitizedUtils.fixedPhone(valueStr));
    179. break;
    180. }
    181. case MOBILE_PHONE: {
    182. field.set(javaBean, DesensitizedUtils.mobilePhone(valueStr));
    183. break;
    184. }
    185. case ADDRESS: {
    186. field.set(javaBean, DesensitizedUtils.address(valueStr, 7));
    187. break;
    188. }
    189. case EMAIL: {
    190. field.set(javaBean, DesensitizedUtils.email(valueStr));
    191. break;
    192. }
    193. case BANK_CARD: {
    194. field.set(javaBean, DesensitizedUtils.bankCard(valueStr));
    195. break;
    196. }
    197. case PASSWORD: {
    198. field.set(javaBean, DesensitizedUtils.password(valueStr));
    199. break;
    200. }case ACCOUNT:{
    201. field.set(javaBean, DesensitizedUtils.account(valueStr));
    202. break;
    203. }
    204. }
    205. }
    206. }
    207. }
    208. /**
    209. * 执行某个对象中指定的方法
    210. *
    211. * @param javaBean 对象
    212. * @param desensitized
    213. * @return
    214. */
    215. private static boolean executeIsEffictiveMethod(Object javaBean, Desensitized desensitized) {
    216. boolean isAnnotationEffictive = true;//注解默认生效
    217. if (desensitized != null) {
    218. String isEffictiveMethod = desensitized.isEffictiveMethod();
    219. if (isNotEmpty(isEffictiveMethod)) {
    220. try {
    221. Method method = javaBean.getClass().getMethod(isEffictiveMethod);
    222. method.setAccessible(true);
    223. isAnnotationEffictive = (Boolean) method.invoke(javaBean);
    224. } catch (NoSuchMethodException e) {
    225. e.printStackTrace();
    226. } catch (IllegalAccessException e) {
    227. e.printStackTrace();
    228. } catch (InvocationTargetException e) {
    229. e.printStackTrace();
    230. }
    231. }
    232. }
    233. return isAnnotationEffictive;
    234. }
    235. private static boolean isNotEmpty(String str) {
    236. return str != null && !"".equals(str);
    237. }
    238. private static boolean isEmpty(String str) {
    239. return !isNotEmpty(str);
    240. }
    241. /**
    242. * 【中文姓名】只显示第一个汉字,其他隐藏为2个星号,比如:李**
    243. *
    244. * @param fullName
    245. * @return
    246. */
    247. public static String chineseName(String fullName) {
    248. if (StringUtils.isBlank(fullName)) {
    249. return "";
    250. }
    251. String name = StringUtils.left(fullName, 1);
    252. return StringUtils.rightPad(name, StringUtils.length(fullName), "*");
    253. }
    254. /**
    255. * 【身份证号】显示第一位和最后一位
    256. *
    257. * @param id
    258. * @return
    259. */
    260. public static String idCardNum(String id) {
    261. if (StringUtils.isBlank(id)) {
    262. return "";
    263. }
    264. return StringUtils.left(id,1).concat(StringUtils.removeStart(StringUtils.leftPad(StringUtils.right(id,1), StringUtils.length(id),"*"),"*"));
    265. }
    266. /**
    267. * 【虚拟账号】显示第一位和最后一位
    268. *
    269. * @param id
    270. * @return
    271. */
    272. public static String account(String id) {
    273. if (StringUtils.isBlank(id)) {
    274. return "";
    275. }
    276. return StringUtils.left(id,1).concat(StringUtils.removeStart(StringUtils.leftPad(StringUtils.right(id,1), StringUtils.length(id),"*"),"*"));
    277. }
    278. /**
    279. * 【固定电话 后四位,其他隐藏,比如1234
    280. *
    281. * @param num
    282. * @return
    283. */
    284. public static String fixedPhone(String num) {
    285. if (StringUtils.isBlank(num)) {
    286. return "";
    287. }
    288. return StringUtils.leftPad(StringUtils.right(num, 4), StringUtils.length(num), "*");
    289. }
    290. /**
    291. * 【手机号码】前三位,后四位,其他隐藏,比如135****6810
    292. *
    293. * @param num
    294. * @return
    295. */
    296. public static String mobilePhone(String num) {
    297. if (StringUtils.isBlank(num)) {
    298. return "";
    299. }
    300. return StringUtils.left(num, 3).concat(StringUtils.removeStart(StringUtils.leftPad(StringUtils.right(num, 4), StringUtils.length(num), "*"), "***"));
    301. }
    302. /**
    303. * 【地址】只显示到地区,不显示详细地址,比如:北京市海淀区****
    304. *
    305. * @param address
    306. * @param sensitiveSize 敏感信息长度
    307. * @return
    308. */
    309. public static String address(String address, int sensitiveSize) {
    310. if (StringUtils.isBlank(address)) {
    311. return "";
    312. }
    313. int length = StringUtils.length(address);
    314. return StringUtils.rightPad(StringUtils.left(address, length - sensitiveSize), length, "*");
    315. }
    316. /**
    317. * 【电子邮箱 邮箱前缀仅显示第一个字母,前缀其他隐藏,用星号代替,@及后面的地址显示,比如:d**@126.com>
    318. *
    319. * @param email
    320. * @return
    321. */
    322. public static String email(String email) {
    323. if (StringUtils.isBlank(email)) {
    324. return "";
    325. }
    326. int index = StringUtils.indexOf(email, "@");
    327. if (index <= 1)
    328. return email;
    329. else
    330. return StringUtils.rightPad(StringUtils.left(email, 1), index, "*").concat(StringUtils.mid(email, index, StringUtils.length(email)));
    331. }
    332. /**
    333. * 【银行卡号】前4位,后3位,其他用星号隐藏每位1个星号,比如:6217 **** **** **** 567>
    334. *
    335. * @param cardNum
    336. * @return
    337. */
    338. public static String bankCard(String cardNum) {
    339. if (StringUtils.isBlank(cardNum)) {
    340. return "";
    341. }
    342. return StringUtils.left(cardNum, 4).concat(StringUtils.removeStart(StringUtils.leftPad(StringUtils.right(cardNum, 3), StringUtils.length(cardNum), "*"), "****"));
    343. }
    344. /**
    345. * 【密码】密码的全部字符都用*代替,比如:******
    346. *
    347. * @param password
    348. * @return
    349. */
    350. public static String password(String password) {
    351. if (StringUtils.isBlank(password)) {
    352. return "";
    353. }
    354. String pwd = StringUtils.left(password, 0);
    355. return StringUtils.rightPad(pwd, StringUtils.length(password), "*");
    356. }
    357. /**
    358. * 遍历List脱敏数据
    359. * @param content
    360. * @return
    361. */
    362. public static <T> List getList(List<T> content){
    363. if (content == null || content.size() <= 0) {
    364. return content;
    365. }
    366. List list = new ArrayList<T>();
    367. for (T t : content) {
    368. list.add(getObj(t));
    369. }
    370. return list;
    371. }
    372. }

    创建脱敏工具类

    1. package com.artboy.project.common.utils;
    2. import com.artboy.project.common.annotation.Desensitized;
    3. import com.google.gson.Gson;
    4. import org.apache.commons.lang3.StringUtils;
    5. import java.lang.reflect.Array;
    6. import java.lang.reflect.Field;
    7. import java.lang.reflect.InvocationTargetException;
    8. import java.lang.reflect.Method;
    9. import java.util.*;
    10. /**
    11. * @author xh
    12. * @Date 2022/7/26
    13. */
    14. public class DesensitizedUtils {
    15. /**
    16. * 获取脱敏json串(递归引用会导致java.lang.StackOverflowError)
    17. *
    18. * @param javaBean
    19. * @return
    20. */
    21. public static String getJson(Object javaBean) {
    22. String json = null;
    23. if (null != javaBean) {
    24. try {
    25. if (javaBean.getClass().isInterface()) return json;
    26. /* 克隆出一个实体进行字段修改,避免修改原实体 */
    27. //Object clone =DesensitizedObjectUtils.deepCloneObject(javaBean);
    28. //Object clone =DesensitizedObjectUtils.deepCloneByFastJson(javaBean);
    29. Object clone = DesensitizedObjectUtils.deepClone(javaBean);
    30. /* 定义一个计数器,用于避免重复循环自定义对象类型的字段 */
    31. Set<Integer> referenceCounter = new HashSet<Integer>();
    32. /* 对克隆实体进行脱敏操作 */
    33. DesensitizedUtils.replace(DesensitizedObjectUtils.getAllFields(clone), clone, referenceCounter);
    34. /* 利用fastjson对脱敏后的克隆对象进行序列化 */
    35. // json = JSON.toJSONString(clone, SerializerFeature.WriteMapNullValue, SerializerFeature.WriteNullListAsEmpty);
    36. json = new Gson().toJson(clone);
    37. /* 清空计数器 */
    38. referenceCounter.clear();
    39. referenceCounter = null;
    40. } catch (Throwable e) {
    41. e.printStackTrace();
    42. }
    43. }
    44. return json;
    45. }
    46. public static <T> T getObj(T javaBean) {
    47. T clone = null;
    48. if (null != javaBean) {
    49. try {
    50. if (javaBean.getClass().isInterface()) return null;
    51. /* 克隆出一个实体进行字段修改,避免修改原实体 */
    52. //Object clone =DesensitizedObjectUtils.deepCloneObject(javaBean);
    53. //Object clone =DesensitizedObjectUtils.deepCloneByFastJson(javaBean);
    54. clone = (T) DesensitizedObjectUtils.deepClone(javaBean);
    55. /* 定义一个计数器,用于避免重复循环自定义对象类型的字段 */
    56. Set<Integer> referenceCounter = new HashSet<Integer>();
    57. /* 对克隆实体进行脱敏操作 */
    58. DesensitizedUtils.replace(DesensitizedObjectUtils.getAllFields(clone), clone, referenceCounter);
    59. /* 清空计数器 */
    60. referenceCounter.clear();
    61. referenceCounter = null;
    62. } catch (Throwable e) {
    63. e.printStackTrace();
    64. }
    65. }
    66. return clone;
    67. }
    68. /**
    69. * 对需要脱敏的字段进行转化
    70. *
    71. * @param fields
    72. * @param javaBean
    73. * @param referenceCounter
    74. * @throws IllegalArgumentException
    75. * @throws IllegalAccessException
    76. */
    77. private static void replace(Field[] fields, Object javaBean, Set<Integer> referenceCounter) throws IllegalArgumentException, IllegalAccessException {
    78. if (null != fields && fields.length > 0) {
    79. for (Field field : fields) {
    80. field.setAccessible(true);
    81. if (null != field && null != javaBean) {
    82. Object value = field.get(javaBean);
    83. if (null != value) {
    84. Class<?> type = value.getClass();
    85. //处理子属性,包括集合中的
    86. if (type.isArray()) {//对数组类型的字段进行递归过滤
    87. int len = Array.getLength(value);
    88. for (int i = 0; i < len; i++) {
    89. Object arrayObject = Array.get(value, i);
    90. if (isNotGeneralType(arrayObject.getClass(), arrayObject, referenceCounter)) {
    91. replace(DesensitizedObjectUtils.getAllFields(arrayObject), arrayObject, referenceCounter);
    92. }
    93. }
    94. } else if (value instanceof Collection<?>) {//对集合类型的字段进行递归过滤
    95. Collection<?> c = (Collection<?>) value;
    96. Iterator<?> it = c.iterator();
    97. while (it.hasNext()) {// TODO: 待优化
    98. Object collectionObj = it.next();
    99. if (isNotGeneralType(collectionObj.getClass(), collectionObj, referenceCounter)) {
    100. replace(DesensitizedObjectUtils.getAllFields(collectionObj), collectionObj, referenceCounter);
    101. }
    102. }
    103. } else if (value instanceof Map<?, ?>) {//对Map类型的字段进行递归过滤
    104. Map<?, ?> m = (Map<?, ?>) value;
    105. Set<?> set = m.entrySet();
    106. for (Object o : set) {
    107. Map.Entry<?, ?> entry = (Map.Entry<?, ?>) o;
    108. Object mapVal = entry.getValue();
    109. if (isNotGeneralType(mapVal.getClass(), mapVal, referenceCounter)) {
    110. replace(DesensitizedObjectUtils.getAllFields(mapVal), mapVal, referenceCounter);
    111. }
    112. }
    113. } else if (value instanceof Enum<?>) {
    114. continue;
    115. }
    116. /*除基础类型、jdk类型的字段之外,对其他类型的字段进行递归过滤*/
    117. else {
    118. if (!type.isPrimitive()
    119. && type.getPackage() != null
    120. && !StringUtils.startsWith(type.getPackage().getName(), "javax.")
    121. && !StringUtils.startsWith(type.getPackage().getName(), "java.")
    122. && !StringUtils.startsWith(field.getType().getName(), "javax.")
    123. && !StringUtils.startsWith(field.getName(), "java.")
    124. && referenceCounter.add(value.hashCode())) {
    125. replace(DesensitizedObjectUtils.getAllFields(value), value, referenceCounter);
    126. }
    127. }
    128. }
    129. //脱敏操作
    130. setNewValueForField(javaBean, field, value);
    131. }
    132. }
    133. }
    134. }
    135. /**
    136. * 排除基础类型、jdk类型、枚举类型的字段
    137. *
    138. * @param clazz
    139. * @param value
    140. * @param referenceCounter
    141. * @return
    142. */
    143. private static boolean isNotGeneralType(Class<?> clazz, Object value, Set<Integer> referenceCounter) {
    144. return !clazz.isPrimitive()
    145. && clazz.getPackage() != null
    146. && !clazz.isEnum()
    147. && !StringUtils.startsWith(clazz.getPackage().getName(), "javax.")
    148. && !StringUtils.startsWith(clazz.getPackage().getName(), "java.")
    149. && !StringUtils.startsWith(clazz.getName(), "javax.")
    150. && !StringUtils.startsWith(clazz.getName(), "java.")
    151. && referenceCounter.add(value.hashCode());
    152. }
    153. /**
    154. * 脱敏操作(按照规则转化需要脱敏的字段并设置新值)
    155. * 目前只支持String类型的字段,如需要其他类型如BigDecimal、Date等类型,可以添加
    156. *
    157. * @param javaBean
    158. * @param field
    159. * @param value
    160. * @throws IllegalAccessException
    161. */
    162. public static void setNewValueForField(Object javaBean, Field field, Object value) throws IllegalAccessException {
    163. //处理自身的属性
    164. Desensitized annotation = field.getAnnotation(Desensitized.class);
    165. if (field.getType().equals(String.class) && null != annotation && executeIsEffictiveMethod(javaBean, annotation)) {
    166. String valueStr = (String) value;
    167. if (StringUtils.isNotBlank(valueStr)) {
    168. switch (annotation.type()) {
    169. case CHINESE_NAME: {
    170. field.set(javaBean, DesensitizedUtils.chineseName(valueStr));
    171. break;
    172. }
    173. case ID_CARD: {
    174. field.set(javaBean, DesensitizedUtils.idCardNum(valueStr));
    175. break;
    176. }
    177. case FIXED_PHONE: {
    178. field.set(javaBean, DesensitizedUtils.fixedPhone(valueStr));
    179. break;
    180. }
    181. case MOBILE_PHONE: {
    182. field.set(javaBean, DesensitizedUtils.mobilePhone(valueStr));
    183. break;
    184. }
    185. case ADDRESS: {
    186. field.set(javaBean, DesensitizedUtils.address(valueStr, 7));
    187. break;
    188. }
    189. case EMAIL: {
    190. field.set(javaBean, DesensitizedUtils.email(valueStr));
    191. break;
    192. }
    193. case BANK_CARD: {
    194. field.set(javaBean, DesensitizedUtils.bankCard(valueStr));
    195. break;
    196. }
    197. case PASSWORD: {
    198. field.set(javaBean, DesensitizedUtils.password(valueStr));
    199. break;
    200. }case ACCOUNT:{
    201. field.set(javaBean, DesensitizedUtils.account(valueStr));
    202. break;
    203. }
    204. }
    205. }
    206. }
    207. }
    208. /**
    209. * 执行某个对象中指定的方法
    210. *
    211. * @param javaBean 对象
    212. * @param desensitized
    213. * @return
    214. */
    215. private static boolean executeIsEffictiveMethod(Object javaBean, Desensitized desensitized) {
    216. boolean isAnnotationEffictive = true;//注解默认生效
    217. if (desensitized != null) {
    218. String isEffictiveMethod = desensitized.isEffictiveMethod();
    219. if (isNotEmpty(isEffictiveMethod)) {
    220. try {
    221. Method method = javaBean.getClass().getMethod(isEffictiveMethod);
    222. method.setAccessible(true);
    223. isAnnotationEffictive = (Boolean) method.invoke(javaBean);
    224. } catch (NoSuchMethodException e) {
    225. e.printStackTrace();
    226. } catch (IllegalAccessException e) {
    227. e.printStackTrace();
    228. } catch (InvocationTargetException e) {
    229. e.printStackTrace();
    230. }
    231. }
    232. }
    233. return isAnnotationEffictive;
    234. }
    235. private static boolean isNotEmpty(String str) {
    236. return str != null && !"".equals(str);
    237. }
    238. private static boolean isEmpty(String str) {
    239. return !isNotEmpty(str);
    240. }
    241. /**
    242. * 【中文姓名】只显示第一个汉字,其他隐藏为2个星号,比如:李**
    243. *
    244. * @param fullName
    245. * @return
    246. */
    247. public static String chineseName(String fullName) {
    248. if (StringUtils.isBlank(fullName)) {
    249. return "";
    250. }
    251. String name = StringUtils.left(fullName, 1);
    252. return StringUtils.rightPad(name, StringUtils.length(fullName), "*");
    253. }
    254. /**
    255. * 【身份证号】显示第一位和最后一位
    256. *
    257. * @param id
    258. * @return
    259. */
    260. public static String idCardNum(String id) {
    261. if (StringUtils.isBlank(id)) {
    262. return "";
    263. }
    264. return StringUtils.left(id,1).concat(StringUtils.removeStart(StringUtils.leftPad(StringUtils.right(id,1), StringUtils.length(id),"*"),"*"));
    265. }
    266. /**
    267. * 【虚拟账号】显示第一位和最后一位
    268. *
    269. * @param id
    270. * @return
    271. */
    272. public static String account(String id) {
    273. if (StringUtils.isBlank(id)) {
    274. return "";
    275. }
    276. return StringUtils.left(id,1).concat(StringUtils.removeStart(StringUtils.leftPad(StringUtils.right(id,1), StringUtils.length(id),"*"),"*"));
    277. }
    278. /**
    279. * 【固定电话 后四位,其他隐藏,比如1234
    280. *
    281. * @param num
    282. * @return
    283. */
    284. public static String fixedPhone(String num) {
    285. if (StringUtils.isBlank(num)) {
    286. return "";
    287. }
    288. return StringUtils.leftPad(StringUtils.right(num, 4), StringUtils.length(num), "*");
    289. }
    290. /**
    291. * 【手机号码】前三位,后四位,其他隐藏,比如135****6810
    292. *
    293. * @param num
    294. * @return
    295. */
    296. public static String mobilePhone(String num) {
    297. if (StringUtils.isBlank(num)) {
    298. return "";
    299. }
    300. return StringUtils.left(num, 3).concat(StringUtils.removeStart(StringUtils.leftPad(StringUtils.right(num, 4), StringUtils.length(num), "*"), "***"));
    301. }
    302. /**
    303. * 【地址】只显示到地区,不显示详细地址,比如:北京市海淀区****
    304. *
    305. * @param address
    306. * @param sensitiveSize 敏感信息长度
    307. * @return
    308. */
    309. public static String address(String address, int sensitiveSize) {
    310. if (StringUtils.isBlank(address)) {
    311. return "";
    312. }
    313. int length = StringUtils.length(address);
    314. return StringUtils.rightPad(StringUtils.left(address, length - sensitiveSize), length, "*");
    315. }
    316. /**
    317. * 【电子邮箱 邮箱前缀仅显示第一个字母,前缀其他隐藏,用星号代替,@及后面的地址显示,比如:d**@126.com>
    318. *
    319. * @param email
    320. * @return
    321. */
    322. public static String email(String email) {
    323. if (StringUtils.isBlank(email)) {
    324. return "";
    325. }
    326. int index = StringUtils.indexOf(email, "@");
    327. if (index <= 1)
    328. return email;
    329. else
    330. return StringUtils.rightPad(StringUtils.left(email, 1), index, "*").concat(StringUtils.mid(email, index, StringUtils.length(email)));
    331. }
    332. /**
    333. * 【银行卡号】前4位,后3位,其他用星号隐藏每位1个星号,比如:6217 **** **** **** 567>
    334. *
    335. * @param cardNum
    336. * @return
    337. */
    338. public static String bankCard(String cardNum) {
    339. if (StringUtils.isBlank(cardNum)) {
    340. return "";
    341. }
    342. return StringUtils.left(cardNum, 4).concat(StringUtils.removeStart(StringUtils.leftPad(StringUtils.right(cardNum, 3), StringUtils.length(cardNum), "*"), "****"));
    343. }
    344. /**
    345. * 【密码】密码的全部字符都用*代替,比如:******
    346. *
    347. * @param password
    348. * @return
    349. */
    350. public static String password(String password) {
    351. if (StringUtils.isBlank(password)) {
    352. return "";
    353. }
    354. String pwd = StringUtils.left(password, 0);
    355. return StringUtils.rightPad(pwd, StringUtils.length(password), "*");
    356. }
    357. /**
    358. * 遍历List脱敏数据
    359. * @param content
    360. * @return
    361. */
    362. public static <T> List getList(List<T> content){
    363. if (content == null || content.size() <= 0) {
    364. return content;
    365. }
    366. List list = new ArrayList<T>();
    367. for (T t : content) {
    368. list.add(getObj(t));
    369. }
    370. return list;
    371. }
    372. }

    测试

    这里我们以UserEntity为例:(一定要序列化)

    1. package com.artboy.project.model.entity;
    2. import com.artboy.project.common.annotation.Desensitized;
    3. import com.artboy.project.common.enums.SensitiveTypeEnum;
    4. import com.baomidou.mybatisplus.annotation.IdType;
    5. import com.baomidou.mybatisplus.annotation.TableField;
    6. import com.baomidou.mybatisplus.annotation.TableId;
    7. import com.baomidou.mybatisplus.annotation.TableLogic;
    8. import com.baomidou.mybatisplus.annotation.TableName;
    9. import java.io.Serializable;
    10. import java.util.Date;
    11. import lombok.Data;
    12. /**
    13. * 用户
    14. *
    15. * @TableName user
    16. */
    17. @TableName(value = "user")
    18. @Data
    19. public class UserEntity implements Serializable {
    20. /**
    21. * id
    22. */
    23. @TableId(type = IdType.AUTO)
    24. private Integer id;
    25. /**
    26. * 用户昵称
    27. */
    28. private String username;
    29. /**
    30. * 账号
    31. */
    32. private String userAccount;
    33. /**
    34. * 用户头像
    35. */
    36. private String avatarUrl;
    37. /**
    38. * 性别
    39. */
    40. private Integer gender;
    41. /**
    42. * 密码
    43. */
    44. @Desensitized(type = SensitiveTypeEnum.PASSWORD)
    45. private String userPassword;
    46. /**
    47. * 电话
    48. */
    49. @Desensitized(type = SensitiveTypeEnum.MOBILE_PHONE)
    50. private String phone;
    51. /**
    52. * 邮箱
    53. */
    54. @Desensitized(type = SensitiveTypeEnum.EMAIL)
    55. private String email;
    56. /**
    57. * 状态 0 - 正常
    58. */
    59. private Integer userStatus;
    60. /**
    61. * 创建时间,数据库已设置自动更新
    62. */
    63. private Date createTime;
    64. /**
    65. * 更新时间,数据库已设置自动更新
    66. */
    67. private Date updateTime;
    68. /**
    69. * 是否删除
    70. */
    71. @TableLogic
    72. private Integer isDelete;
    73. /**
    74. * 用户角色 0 - 普通用户 1 - 管理员
    75. */
    76. private Integer userRole;
    77. @TableField(exist = false)
    78. private static final long serialVersionUID = 1L;
    79. }

    测试方法:

    1. QueryWrapper<UserEntity> queryWrapper = new QueryWrapper<>();
    2. UserEntity user = userMapper.selectOne(queryWrapper);
    3. UserEntity safetyUser = DesensitizedUtils.getObj(user);

    Springboot切面+注解实现数据脱敏

    处理步骤

    新建脱敏的枚举类

    1. package com.fwy.common.config.dataRule;
    2. import lombok.Getter;
    3. /**
    4. * @description:隐私数据类型枚举
    5. * @author: xiaYZ
    6. * @createDate: 2022/6/21
    7. */
    8. @Getter
    9. public enum PrivacyTypeEnum {
    10. /** 自定义(此项需设置脱敏的范围)*/
    11. CUSTOMER,
    12. /** 姓名 */
    13. NAME,
    14. /** 身份证号 */
    15. ID_CARD,
    16. /** 手机号 */
    17. PHONE,
    18. /** 邮箱 */
    19. EMAIL,
    20. }

    新建脱敏操作的工具类

    1. package com.fwy.common.config.dataRule;
    2. /**
    3. * @description:
    4. * @author: xiaYZ
    5. * @createDate: 2022/6/21
    6. */
    7. public class PrivacyUtil {
    8. /**
    9. * 隐藏手机号中间四位
    10. */
    11. public static String hidePhone(String phone) {
    12. return phone.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2");
    13. }
    14. /**
    15. * 隐藏邮箱
    16. */
    17. public static String hideEmail(String email) {
    18. return email.replaceAll("(\\w?)(\\w+)(\\w)(@\\w+\\.[a-z]+(\\.[a-z]+)?)", "$1****$3$4");
    19. }
    20. /**
    21. * 隐藏身份证
    22. */
    23. public static String hideIDCard(String idCard) {
    24. return idCard.replaceAll("(\\d{4})\\d{10}(\\w{4})", "$1*****$2");
    25. }
    26. /**
    27. * 【中文姓名】只显示第一个汉字,其他隐藏为星号,比如:任**
    28. */
    29. public static String hideChineseName(String chineseName) {
    30. if (chineseName == null) {
    31. return null;
    32. }
    33. return desValue(chineseName, 1, 0, "*");
    34. }
    35. // /**
    36. // * 【身份证号】显示前4位, 后2
    37. // */
    38. // public static String hideIdCard(String idCard) {
    39. // return desValue(idCard, 4, 2, "*");
    40. // }
    41. // /**
    42. // * 【手机号码】前三位,后四位,其他隐藏。
    43. // */
    44. // public static String hidePhone(String phone) {
    45. // return desValue(phone, 3, 4, "*");
    46. // }
    47. /**
    48. * 对字符串进行脱敏操作
    49. * @param origin 原始字符串
    50. * @param prefixNoMaskLen 左侧需要保留几位明文字段
    51. * @param suffixNoMaskLen 右侧需要保留几位明文字段
    52. * @param maskStr 用于遮罩的字符串, 如'*'
    53. * @return 脱敏后结果
    54. */
    55. public static String desValue(String origin, int prefixNoMaskLen, int suffixNoMaskLen, String maskStr) {
    56. if (origin == null) {
    57. return null;
    58. }
    59. StringBuilder sb = new StringBuilder();
    60. for (int i = 0, n = origin.length(); i < n; i++) {
    61. if (i < prefixNoMaskLen) {
    62. sb.append(origin.charAt(i));
    63. continue;
    64. }
    65. if (i > (n - suffixNoMaskLen - 1)) {
    66. sb.append(origin.charAt(i));
    67. continue;
    68. }
    69. sb.append(maskStr);
    70. }
    71. return sb.toString();
    72. }
    73. }

    申明注解类

    1. package com.fwy.common.config.dataRule;
    2. import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
    3. import com.fasterxml.jackson.databind.annotation.JsonSerialize;
    4. import java.lang.annotation.ElementType;
    5. import java.lang.annotation.Retention;
    6. import java.lang.annotation.RetentionPolicy;
    7. import java.lang.annotation.Target;
    8. /**
    9. * @description: 自定义数据脱敏注解
    10. * @author: xiaYZ
    11. * @createDate: 2022/6/21
    12. */
    13. @Target(ElementType.FIELD) // 作用在字段上
    14. @Retention(RetentionPolicy.RUNTIME) // class文件中保留,运行时也保留,能通过反射读取到
    15. @JacksonAnnotationsInside // 表示自定义自己的注解PrivacyEncrypt
    16. @JsonSerialize(using = PrivacySerializer.class) // 该注解使用序列化的方式
    17. public @interface PrivacyEncrypt {
    18. /**
    19. * 脱敏数据类型(没给默认值,所以使用时必须指定type)
    20. */
    21. PrivacyTypeEnum type();
    22. /**
    23. * 前置不需要打码的长度
    24. */
    25. int prefixNoMaskLen() default 1;
    26. /**
    27. * 后置不需要打码的长度
    28. */
    29. int suffixNoMaskLen() default 1;
    30. /**
    31. * 用什么打码
    32. */
    33. String symbol() default "*";
    34. }

    注解的AOP操作

    1. package com.fwy.common.config.dataRule;
    2. import com.fasterxml.jackson.core.JsonGenerator;
    3. import com.fasterxml.jackson.databind.BeanProperty;
    4. import com.fasterxml.jackson.databind.JsonMappingException;
    5. import com.fasterxml.jackson.databind.JsonSerializer;
    6. import com.fasterxml.jackson.databind.SerializerProvider;
    7. import com.fasterxml.jackson.databind.ser.ContextualSerializer;
    8. import lombok.AllArgsConstructor;
    9. import lombok.NoArgsConstructor;
    10. import org.apache.commons.lang3.StringUtils;
    11. import java.io.IOException;
    12. import java.util.Objects;
    13. /**
    14. * @description:
    15. * @author: xiaYZ
    16. * @createDate: 2022/6/21
    17. */
    18. @NoArgsConstructor
    19. @AllArgsConstructor
    20. public class PrivacySerializer extends JsonSerializer implements ContextualSerializer {
    21. // 脱敏类型
    22. private PrivacyTypeEnum privacyTypeEnum;
    23. // 前几位不脱敏
    24. private Integer prefixNoMaskLen;
    25. // 最后几位不脱敏
    26. private Integer suffixNoMaskLen;
    27. // 用什么打码
    28. private String symbol;
    29. @Override
    30. public void serialize(final String origin, final JsonGenerator jsonGenerator,
    31. final SerializerProvider serializerProvider) throws IOException {
    32. if (StringUtils.isNotBlank(origin) && null != privacyTypeEnum) {
    33. switch (privacyTypeEnum) {
    34. case CUSTOMER:
    35. jsonGenerator.writeString(PrivacyUtil.desValue(origin, prefixNoMaskLen, suffixNoMaskLen, symbol));
    36. break;
    37. case NAME:
    38. jsonGenerator.writeString(PrivacyUtil.hideChineseName(origin));
    39. break;
    40. case ID_CARD:
    41. jsonGenerator.writeString(PrivacyUtil.hideIDCard(origin));
    42. break;
    43. case PHONE:
    44. jsonGenerator.writeString(PrivacyUtil.hidePhone(origin));
    45. break;
    46. case EMAIL:
    47. jsonGenerator.writeString(PrivacyUtil.hideEmail(origin));
    48. break;
    49. default:
    50. throw new IllegalArgumentException("unknown privacy type enum " + privacyTypeEnum);
    51. }
    52. }
    53. }
    54. @Override
    55. public JsonSerializer createContextual(final SerializerProvider serializerProvider,
    56. final BeanProperty beanProperty) throws JsonMappingException {
    57. if (beanProperty != null) {
    58. if (Objects.equals(beanProperty.getType().getRawClass(), String.class)) {
    59. PrivacyEncrypt privacyEncrypt = beanProperty.getAnnotation(PrivacyEncrypt.class);
    60. if (privacyEncrypt == null) {
    61. privacyEncrypt = beanProperty.getContextAnnotation(PrivacyEncrypt.class);
    62. }
    63. if (privacyEncrypt != null) {
    64. return new PrivacySerializer(privacyEncrypt.type(), privacyEncrypt.prefixNoMaskLen(),
    65. privacyEncrypt.suffixNoMaskLen(), privacyEncrypt.symbol());
    66. }
    67. }
    68. return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);
    69. }
    70. return serializerProvider.findNullValueSerializer(null);
    71. }
    72. }

    实际使用

    在这里插入图片描述

    基于jackson,通过自定义注解的方式实现数据脱敏

    添加依赖

    spring-web、spring-boot-starter-web已经集成了jackson相关包,不用添加

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

    脱敏注解
    DesensitizationJsonSerializer.class: 脱敏序列化类

    1. @Retention(RetentionPolicy.RUNTIME)
    2. @JacksonAnnotationsInside
    3. @JsonSerialize(using = DesensitizationJsonSerializer.class)
    4. public @interface Desensitization {
    5.     Classextends AbstractDesensitization> value();
    6. }

    脱敏序列化

    1. /**
    2.  * 脱敏序列化
    3.  */
    4. public class DesensitizationJsonSerializer extends JsonSerializer<String> implements ContextualSerializer {
    5.     private AbstractDesensitization desensitization;
    6.     public DesensitizationJsonSerializer() {
    7.     }
    8.     public DesensitizationJsonSerializer(AbstractDesensitization desensitization) {
    9.         this.desensitization = desensitization;
    10.     }
    11.     @Override
    12.     public void serialize(String s, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
    13.         jsonGenerator.writeString(desensitization.serialize(s));;
    14.     }
    15.     @Override
    16.     public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {
    17.         JsonSerializer<?> jsonSerializer = null;
    18.         if(null == beanProperty) jsonSerializer = serializerProvider.findNullValueSerializer(beanProperty);
    19.         if(!Objects.equals(beanProperty.getType().getRawClass(), String.class))
    20.             jsonSerializer = serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);
    21.         if(Objects.equals(beanProperty.getType().getRawClass(), String.class)){
    22.             jsonSerializer = setDesensitization(jsonSerializer, beanProperty);
    23.         }
    24.         return jsonSerializer;
    25.     }
    26.     /**
    27.      * 设置脱敏
    28.      * @param beanProperty
    29.      * @return
    30.      */
    31.     private JsonSerializer<?> setDesensitization(JsonSerializer<?> jsonSerializer, BeanProperty beanProperty) {
    32.         Desensitization desensitization = beanProperty.getAnnotation(Desensitization.class);
    33.         if (desensitization == null) desensitization = beanProperty.getContextAnnotation(Desensitization.class);
    34.         if (desensitization != null) {
    35.             //设置脱敏实例
    36.             try {
    37.                 jsonSerializer = new DesensitizationJsonSerializer(desensitization.value().newInstance());
    38.             } catch (InstantiationException e) {
    39.                 e.printStackTrace();
    40.             } catch (IllegalAccessException e) {
    41.                 e.printStackTrace();
    42.             }
    43.         }
    44.         return jsonSerializer;
    45.     }
    46. }

    脱敏类
    脱敏父类
    子类通过继承AbstractDesensitization实现扩展

    1. public abstract class AbstractDesensitization {
    2.     /**
    3.      * 脱敏
    4.      * @param value
    5.      * @return
    6.      */
    7.     public abstract String serialize(String value);
    8. }

    中文姓名脱敏

    1. public class ChineseNameDesensitization extends AbstractDesensitization {
    2.     @Override
    3.     public String serialize(String value) {
    4.         String serializeValue = "";
    5.         if(value.length() < 3){
    6.             serializeValue = value.replaceAll(".*(?=[\\u4e00-\\u9fa5])","*");
    7.         }else{
    8.             serializeValue = value.replaceAll("(?<=[\\u4e00-\\u9fa5]).*(?=[\\u4e00-\\u9fa5])","*");
    9.         }
    10.         return serializeValue;
    11.     }
    12. }

    手机号脱敏

    1. public class MobilePhoneDesensitization extends AbstractDesensitization {
    2.     @Override
    3.     public String serialize(String value) {
    4.         return value.replaceAll("(\\d{3})\\d{4}(\\d{4})","$1****$2");
    5.     }
    6. }

    身份证脱敏

    1. public class IdCardDesensitization extends AbstractDesensitization {
    2.     @Override
    3.     public String serialize(String value) {
    4.         return value.replaceAll("(?<=\\w{3})\\w(?=\\w{4})","*");
    5.     }
    6. }

    测试
    添加脱敏注解

    1. public class User {
    2.     @Desensitization(ChineseNameDesensitization.class)
    3.     private String name;
    4.     private Integer age;
    5.     @Desensitization(IdCardDesensitization.class)
    6.     private String idCard;
    7.     @Desensitization(MobilePhoneDesensitization.class)
    8.     private String mobilePhone;
    9.     //...get and set
    10. }

    新建UserController,查询用户信息

    1. @RestController
    2. public class UserController {
    3.     @GetMapping("/users")
    4.     private List<User> users() throws Exception {
    5.         List<User> girls = new ArrayList<>();
    6.         User user = new User();
    7.         user.setName("西施");
    8.         user.setAge(18);
    9.         user.setIdCard("123456789123456202");
    10.         user.setMobilePhone("12345678901");
    11.         User user2 = new User();
    12.         user2.setName("杨贵妃");
    13.         user2.setAge(18);
    14.         user2.setIdCard("123456789123456202");
    15.         user2.setMobilePhone("12345678901");
    16.         User user3 = new User();
    17.         user3.setName("古代四大美女之一 * 貂蝉");
    18.         user3.setAge(18);
    19.         user3.setIdCard("123456789123456202");
    20.         user3.setMobilePhone("12345678901");
    21.         User user4 = new User();
    22.         user4.setName("古代四大美女之一 * 王昭君");
    23.         user4.setAge(18);
    24.         user4.setIdCard("123456789123456202");
    25.         user4.setMobilePhone("12345678901");
    26.         User user5 = new User();
    27.         user5.setName(null);
    28.         user5.setAge(18);
    29.         user5.setIdCard(null);
    30.         user5.setMobilePhone(null);
    31.         girls.add(user);
    32.         girls.add(user2);
    33.         girls.add(user3);
    34.         girls.add(user4);
    35.         girls.add(user5);
    36.         return girls;
    37.     }
    38. }

  • 相关阅读:
    【数仓基础(一)】基础概念:数据仓库【用于决策的数据集合】的概念、建立数据仓库的原因与好处
    Go语言结构体指针
    BI零售数据分析方案,看了就想拥有
    5 分钟带你小程序入门 [实战总结分享]
    2023-09-27 Cmake 编译 OpenCV+Contrib 源码通用设置
    Leetcode 1834. Single-Threaded CPU (堆好题)
    C语言学生成绩管理系统
    1034 Head of a Gang
    深入研究下Spring Boot Actuator 在kubernetes中探针的应用
    12年开发大佬,熬夜4个月整理的SpringBoot实战派,绝对涨薪秘籍
  • 原文地址:https://blog.csdn.net/weixin_53998054/article/details/126741551