Java 数据脱敏
数据脱敏#
数据脱敏又称数据去隐私化或数据变形,是在给定的规则、策略下对敏感数据进行变换、修改的技术机制,能够在很大程度上解决敏感数据在非可信环境中使用的问题。根据数据保护规范和脱敏策略.对业务数据中的敏感信息实施自动变形.实现对敏感信息的隐藏。
脱敏方法#
项目是在controller层进行脱敏,查阅google和github有两种较为方便的方法
一种是基于注解 desensitized基于注解的形式进行脱敏 GitHub
一种是基于框架本质上还是注解,但是已经封装好了,当然还提供fastjson的方式进行脱敏 sensitive框架进行脱敏 GitHub
由于项目原因不想引入框架,所以使用基于注解的方式
实现#
新建枚举类型,标明是何种脱敏方式
Copy
package com.blgroup.vision.common.enums;
/**
* @program: blgroup-scloud-web
* @description: 脱敏枚举
* @author: ingxx
* @create: 2019-12-17 14:32
**/
public enum SensitiveTypeEnum {
/**
* 中文名
*/
CHINESE_NAME,
/**
* 身份证号
*/
ID_CARD,
/**
* 座机号
*/
FIXED_PHONE,
/**
* 手机号
*/
MOBILE_PHONE,
/**
* 地址
*/
ADDRESS,
/**
* 电子邮件
*/
EMAIL,
/**
* 银行卡
*/
BANK_CARD,
/**
* 虚拟账号
*/
ACCOUNT,
/**
* 密码
*/
PASSWORD;
}
创建注解,标识需要脱敏的字段
Copy
package com.blgroup.vision.common.annotation;
import com.blgroup.vision.common.enums.SensitiveTypeEnum;
import java.lang.annotation.*;
/**
* @program: blgroup-scloud-web
* @description: 脱敏注解
* @author: ingxx
* @create: 2019-12-17 14:32
**/
@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Desensitized {
/*脱敏类型(规则)*/
SensitiveTypeEnum type();
/*判断注解是否生效的方法*/
String isEffictiveMethod() default "";
}
创建Object工具类,用于复制对象和对对象的其他操作,注意使用fastjson实现深拷贝对于复杂的对象会出现栈溢出,本人测试是发生在转换对象时对象会转换成JsonObject对象,在getHash方法中出现递归调用导致栈溢出,具体原因不明
Copy
package com.blgroup.vision.common.utils;
import com.alibaba.fastjson.JSON;
import org.apache.commons.lang3.StringUtils;
import java.io.*;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.util.*;
/**
* @program: blgroup-scloud-web
* @description: 脱敏Object工具类
* @author: ingxx
* @create: 2019-12-17 14:32
**/
public class DesensitizedObjectUtils {
/**
* 用序列化-反序列化方式实现深克隆
* 缺点:1、被拷贝的对象必须要实现序列化
*
* @param obj
* @return
*/
@SuppressWarnings("unchecked")
public static T deepCloneObject(T obj) {
T t = (T) new Object();
try {
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(byteOut);
out.writeObject(obj);
out.close();
ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());
ObjectInputStream in = new ObjectInputStream(byteIn);
t = (T) in.readObject();
in.close();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return t;
}
/**
* 用序列化-反序列化的方式实现深克隆
* 缺点:1、当实体中存在接口类型的参数,并且这个接口指向的实例为枚举类型时,会报错"com.alibaba.fastjson.JSONException: syntax error, expect {, actual string, pos 171, fieldName iLimitKey"
*
* @param objSource
* @return
*/
public static Object deepCloneByFastJson(Object objSource) {
String tempJson = JSON.toJSONString(objSource);
Object clone = JSON.parseObject(tempJson, objSource.getClass());
return clone;
}
/**
* 深度克隆对象
*
* @throws IllegalAccessException
* @throws InstantiationException
*/
public static Object deepClone(Object objSource) throws InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
if (null == objSource) return null;
//是否jdk类型、基础类型、枚举类型
if (isJDKType(objSource.getClass())
|| objSource.getClass().isPrimitive()
|| objSource instanceof Enum>) {
if ("java.lang.String".equals(objSource.getClass().getName())) {//目前只支持String类型深复制
return new String((String) objSource);
} else {
return objSource;
}
}
// 获取源对象类型
Class> clazz = objSource.getClass();
Object objDes = clazz.newInstance();
// 获得源对象所有属性
Field[] fields = getAllFields(objSource);
// 循环遍历字段,获取字段对应的属性值
for (Field field : fields) {
field.setAccessible(true);
if (null == field) continue;
Object value = field.get(objSource);
if (null == value) continue;
Class> type = value.getClass();
if (isStaticFinal(field)) {
continue;
}
try {
//遍历集合属性
if (type.isArray()) {//对数组类型的字段进行递归过滤
int len = Array.getLength(value);
if (len < 1) continue;
Class> c = value.getClass().getComponentType();
Array newArray = (Array) Array.newInstance(c, len);
for (int i = 0; i < len; i++) {
Object arrayObject = Array.get(value, i);
Array.set(newArray, i, deepClone(arrayObject));
}
} else if (value instanceof Collection>) {
Collection newCollection = (Collection) value.getClass().newInstance();
Collection> c = (Collection>) value;
Iterator> it = c.iterator();
while (it.hasNext()) {
Object collectionObj = it.next();
newCollection.add(deepClone(collectionObj));
}
field.set(objDes, newCollection);
continue;
} else if (value instanceof Map, ?>) {
Map newMap = (Map) value.getClass().newInstance();
Map, ?> m = (Map, ?>) value;
Set> set = m.entrySet();
for (Object o : set) {
Map.Entry, ?> entry = (Map.Entry, ?>) o;
Object mapVal = entry.getValue();
newMap.put(entry.getKey(), deepClone(mapVal));
}
field.set(objDes, newMap);
continue;
}
//是否jdk类型或基础类型
if (isJDKType(field.get(objSource).getClass())
|| field.getClass().isPrimitive()
|| isStaticType(field)
|| value instanceof Enum>) {
if ("java.lang.String".equals(value.getClass().getName())) {//目前只支持String类型深复制
field.set(objDes, new String((String) value));
} else {
field.set(objDes, field.get(objSource));
}
continue;
}
//是否枚举
if (value.getClass().isEnum()) {
field.set(objDes, field.get(objSource));
continue;
}
//是否自定义类
if (isUserDefinedType(value.getClass())) {
field.set(objDes, deepClone(value));
continue;
}
} catch (Exception e) {
e.printStackTrace();
}
}
return objDes;
}
/**
* 是否静态变量
*
* @param field
* @return
*/
private static boolean isStaticType(Field field) {
return field.getModifiers() == 8 ? true : false;
}
private static boolean isStaticFinal(Field field) {
return Modifier.isFinal(field.getModifiers()) && Modifier.isStatic(field.getModifiers());
}
/**
* 是否jdk类型变量
*
* @param clazz
* @return
* @throws IllegalAccessException
*/
private static boolean isJDKType(Class clazz) throws IllegalAccessException {
//Class clazz = field.get(objSource).getClass();
return org.apache.commons.lang3.StringUtils.startsWith(clazz.getPackage().getName(), "javax.")
|| org.apache.commons.lang3.StringUtils.startsWith(clazz.getPackage().getName(), "java.")
|| org.apache.commons.lang3.StringUtils.startsWith(clazz.getName(), "javax.")
|| org.apache.commons.lang3.StringUtils.startsWith(clazz.getName(), "java.");
}
/**
* 是否用户自定义类型
*
* @param clazz
* @return
*/
private static boolean isUserDefinedType(Class> clazz) {
return
clazz.getPackage() != null
&& !org.apache.commons.lang3.StringUtils.startsWith(clazz.getPackage().getName(), "javax.")
&& !org.apache.commons.lang3.StringUtils.startsWith(clazz.getPackage().getName(), "java.")
&& !org.apache.commons.lang3.StringUtils.startsWith(clazz.getName(), "javax.")
&& !StringUtils.startsWith(clazz.getName(), "java.");
}
/**
* 获取包括父类所有的属性
*
* @param objSource
* @return
*/
public static Field[] getAllFields(Object objSource) {
/*获得当前类的所有属性(private、protected、public)*/
List fieldList = new ArrayList();
Class tempClass = objSource.getClass();
while (tempClass != null && !tempClass.getName().toLowerCase().equals("java.lang.object")) {//当父类为null的时候说明到达了最上层的父类(Object类).
fieldList.addAll(Arrays.asList(tempClass.getDeclaredFields()));
tempClass = tempClass.getSuperclass(); //得到父类,然后赋给自己
}
Field[] fields = new Field[fieldList.size()];
fieldList.toArray(fields);
return fields;
}
/**
* 深度克隆对象
*
* @throws IllegalAccessException
* @throws InstantiationException
*/
@Deprecated
public static Object copy(Object objSource) throws InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
if (null == objSource) return null;
// 获取源对象类型
Class> clazz = objSource.getClass();
Object objDes = clazz.newInstance();
// 获得源对象所有属性
Field[] fields = getAllFields(objSource);
// 循环遍历字段,获取字段对应的属性值
for (Field field : fields) {
field.setAccessible(true);
// 如果该字段是 static + final 修饰
if (field.getModifiers() >= 24) {
continue;
}
try {
// 设置字段可见,即可用get方法获取属性值。
field.set(objDes, field.get(objSource));
} catch (Exception e) {
e.printStackTrace();
}
}
return objDes;
}
}
创建脱敏工具类
Copy
package com.blgroup.vision.common.utils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.blgroup.vision.common.annotation.Desensitized;
import org.apache.commons.lang3.StringUtils;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
/**
* @program: blgroup-scloud-web
* @description: 脱敏工具类
* @author: ingxx
* @create: 2019-12-17 14:32
**/
public class DesensitizedUtils{
/**
* 获取脱敏json串(递归引用会导致java.lang.StackOverflowError)
*
* @param javaBean
* @return
*/
public static String getJson(Object javaBean) {
String json = null;
if (null != javaBean) {
try {
if (javaBean.getClass().isInterface()) return json;
/* 克隆出一个实体进行字段修改,避免修改原实体 */
//Object clone =DesensitizedObjectUtils.deepCloneObject(javaBean);
//Object clone =DesensitizedObjectUtils.deepCloneByFastJson(javaBean);
Object clone = DesensitizedObjectUtils.deepClone(javaBean);
/* 定义一个计数器,用于避免重复循环自定义对象类型的字段 */
Set referenceCounter = new HashSet();
/* 对克隆实体进行脱敏操作 */
DesensitizedUtils.replace(DesensitizedObjectUtils.getAllFields(clone), clone, referenceCounter);
/* 利用fastjson对脱敏后的克隆对象进行序列化 */
json = JSON.toJSONString(clone, SerializerFeature.WriteMapNullValue, SerializerFeature.WriteNullListAsEmpty);
/* 清空计数器 */
referenceCounter.clear();
referenceCounter = null;
} catch (Throwable e) {
e.printStackTrace();
}
}
return json;
}
public static T getObj(T javaBean) {
T clone = null;
if (null != javaBean) {
try {
if (javaBean.getClass().isInterface()) return null;
/* 克隆出一个实体进行字段修改,避免修改原实体 */
//Object clone =DesensitizedObjectUtils.deepCloneObject(javaBean);
//Object clone =DesensitizedObjectUtils.deepCloneByFastJson(javaBean);
clone = (T) DesensitizedObjectUtils.deepClone(javaBean);
/* 定义一个计数器,用于避免重复循环自定义对象类型的字段 */
Set referenceCounter = new HashSet();
/* 对克隆实体进行脱敏操作 */
DesensitizedUtils.replace(DesensitizedObjectUtils.getAllFields(clone), clone, referenceCounter);
/* 清空计数器 */
referenceCounter.clear();
referenceCounter = null;
} catch (Throwable e) {
e.printStackTrace();
}
}
retur