可以解决脱敏实体对象的循环依赖(控制遍历的深度),和实体相互引用的脱敏。
/**
* 敏感数据类型
* @author jiangwenjie
* @date 2021/1/26
*/
public enum EnumSensitiveType {
/**
* 标志这个是一个需要脱敏的实体类
*/
ENITTY_CLASS,
/**
* 中文名
*/
CHINESE_NAME,
/**
* 身份证号
*/
ID_CARD,
/**
* 手机号
*/
MOBILE_PHONE,
/**
* 地址
*/
ADDRESS,
/**
* 电子邮件
*/
EMAIL,
/**
* 银行卡
*/
BANK_CARD;
}
/**
* 敏感数据脱敏注解
*
* @author jiangwenjie
* @date 2021/1/26
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Desensitized {
/**
* 脱敏类型 {@EnumSensitiveType}
*/
EnumSensitiveType type();
}
/**
* 敏感数据脱敏处理工具类
* @author jiangwenjie
* @date 2021/1/26
*/
@Slf4j
public class DesensitizedUtils {
/**
* 递归深度计数器:防止循环依赖
*/
private static ThreadLocal<AtomicInteger> threadLocalCounter = new ThreadLocal<>();
/**
* 递归的最大深度
*/
private static final Integer MAX_DEPTH = 3;
/**
* 敏感数据脱敏
* 脱敏对象 的字段 需要添加 {@Sensitive} 注解并标注敏感类型
* @param bean
* @return
*/
public static Object desensitization(Object bean) {
try {
// 控制递归的深度:防止循环依赖
threadLocalCounter.set(new AtomicInteger(MAX_DEPTH));
doDesensitization(bean);
} catch (Exception e) {
log.warn("数据脱敏工具异常",e);
}finally {
threadLocalCounter.remove();
}
return bean;
}
/**
* 遍历递归脱敏操作
* @param bean
* @throws Exception
*/
private static void doDesensitization(Object bean) throws Exception {
if (Objects.isNull(bean) || isBeyoudMaxDepth(threadLocalCounter.get())) {
return;
}
//处理子属性,包括集合中的
Object object = null;
if (bean.getClass().isArray()) {
//对数组类型的字段进行递归过滤
for (int i = 0; i < Array.getLength(bean); i++) {
object = Array.get(bean, i);
if (isNotGeneralType(object.getClass())) {
desensitizationField(object);
}
}
} else if (bean instanceof Collection<?>) {
//对集合类型的字段进行递归过滤
Iterator<?> it = ((Collection<?>) bean).iterator();
while (it.hasNext()) {
object = it.next();
if (isNotGeneralType(object.getClass())) {
desensitizationField(object);
}
}
} else if (bean instanceof Map<?, ?>) {
//对Map类型的字段进行递归过滤
Map<?, ?> map = (Map<?, ?>) bean;
for (Object o : map.entrySet()) {
object = ((Map.Entry<?, ?>) o).getValue();
if (isNotGeneralType(object.getClass())) {
desensitizationField(object);
}
}
} else {//自定义类等
if (isNotGeneralType(bean.getClass())) {
desensitizationField(bean);
}
}
}
/**
* 脱敏bean所有属性
* @param bean
* @throws Exception
*/
private static void desensitizationField(Object bean) throws Exception{
// 获取对象的所有属性
List<Field> fields = getAllFields(bean);
if (CollectionUtils.isEmpty(fields)){
return;
}
Object value;
for (Field field:fields) {
field.setAccessible(true);
value = field.get(bean);
// 属性是final类型 不处理
if (Objects.isNull(value)) {
continue;
}
//不是基本数据类型
if (isNotGeneralType(value.getClass())) {
doDesensitization(value);
}else{
//脱敏操作
setNewValueForField(bean, field, value);
}
}
}
/**
* 获取包括父类所有 使用了{@Desensitized}注解的属性
* @param objSource
* @return
*/
private static List<Field> getAllFields(Object objSource) {
if(Objects.isNull(objSource)){
return null;
}
AtomicInteger atomicInteger = new AtomicInteger(MAX_DEPTH);
// 获得当前类的所有属性(private、protected、public)
List<Field> fieldList = Lists.newArrayList();
Class tempClass = objSource.getClass();
while (Objects.nonNull(tempClass) && !tempClass.getName().toLowerCase().equals("java.lang.object") && !isBeyoudMaxDepth(atomicInteger)) {
fieldList.addAll(Arrays.asList(tempClass.getDeclaredFields()));
//得到父类,然后赋给自己
tempClass = tempClass.getSuperclass();
}
// 过滤出来需要
return fieldList.stream().filter(field -> !Modifier.isFinal(field.getModifiers()) && Objects.nonNull(field.getAnnotation(Desensitized.class))).collect(Collectors.toList());
}
/**
* 排除基础类型、枚举类型、扩展库类型的字段
* @param clazz
* @return
*/
private static boolean isNotGeneralType(Class<?> clazz) {
return !clazz.isPrimitive()
&& !clazz.isEnum()
&& Objects.nonNull(clazz.getPackage())
&& !StringUtils.startsWith(clazz.getPackage().getName(), "javax.")
&& !StringUtils.startsWith(clazz.getPackage().getName(), "java.lang")
&& !StringUtils.startsWith(clazz.getPackage().getName(), "java.time");
}
/**
* 脱敏操作(按照规则转化需要脱敏的字段并设置新值)
* 目前只支持String类型的字段
* @param bean
* @param field
* @param value
* @throws IllegalAccessException
*/
public static void setNewValueForField(Object bean, Field field, Object value) {
try {
//处理自身的属性
Desensitized annotation = field.getAnnotation(Desensitized.class);
String valueStr;
if (!field.getType().equals(String.class) || Objects.isNull(value)
|| StringUtils.isEmpty(valueStr = value.toString()) || Objects.isNull(annotation)) {
return;
}
switch (annotation.type()) {
case CHINESE_NAME: {
field.set(bean, chineseName(valueStr));
break;
}
case ID_CARD: {
field.set(bean, idCardNum(valueStr));
break;
}
case MOBILE_PHONE: {
field.set(bean, mobilePhone(valueStr));
break;
}
case ADDRESS: {
field.set(bean, address(valueStr, 7));
break;
}
case EMAIL: {
field.set(bean, email(valueStr));
break;
}
case BANK_CARD: {
field.set(bean, bankCard(valueStr));
break;
} default:
break;
}
}catch (Exception e){
log.warn("数据脱敏工具异常,bean:{},field:{},value:{}", GsonUtils.obj2Json(bean),field.getName(),value,e);
}
}
private static Boolean isBeyoudMaxDepth(AtomicInteger atomicInteger){
if (Objects.isNull(atomicInteger)){
return Boolean.FALSE;
}
final int increment = atomicInteger.getAndDecrement();
if (increment <= 0){
return Boolean.TRUE;
}
return Boolean.FALSE;
}
/**
* 【银行卡号】数据脱敏
* 只留前四位和后四位
* 6227 0383 3938 3938 393 脱敏结果: 6227 **** **** ***8 393
*
* @param value
* @return
*/
public static String bankCard(String value) {
if (StringUtils.isEmpty(value)) {
return null;
}
return StringUtils.left(value, 4).concat(StringUtils.removeStart(StringUtils.leftPad(StringUtils.right(value, 4), StringUtils.length(value), "*"), "***"));
}
/**
* 【邮箱】数据脱敏
* 邮箱前缀仅显示第一个字母,前缀其他隐藏,用星号代替,@及后面的地址显示
* 例子:g**@163.com
*
* @param value
* @return
*/
public static String email(String value) {
if (StringUtils.isEmpty(value)) {
return null;
}
int index = StringUtils.indexOf(value, "@");
if (index <= 1) {
return value;
} else {
return StringUtils.rightPad(StringUtils.left(value, 1), index, "*").concat(
StringUtils.mid(value, index, StringUtils.length(value)));
}
}
/**
* 【身份证号】脱敏类型
* 前3位,后4位
* 130722199102323232 脱敏后: 130*************3232
*
* @param value
* @return
*/
public static String idCardNum(String value) {
if (StringUtils.isEmpty(value)) {
return null;
}
return StringUtils.left(value, 3).concat(StringUtils.removeStart(StringUtils.leftPad(StringUtils.right(value, 4), StringUtils.length(value), "*"), "***"));
}
/**
* 【手机号码】数据脱敏
* 18233583070 脱敏后: 182****3030
*
* @param value
* @return
*/
public static String mobilePhone(String value) {
if (StringUtils.isEmpty(value)) {
return null;
}
return StringUtils.left(value, 3).concat(StringUtils.removeStart(StringUtils.leftPad(StringUtils.right(value, 4), StringUtils.length(value), "*"), "***"));
}
/**
* 【姓名】真实姓名脱敏
* 中文姓名只显示最后一个汉字,其他隐藏为星号
* 张三丰 :**丰
*
* @param value
* @return
*/
public static String chineseName(String value) {
if (StringUtils.isEmpty(value)) {
return null;
}
return StringUtils.leftPad(StringUtils.right(value, 1),StringUtils.length(value) , "*");
}
/**
* 【地址】只显示到地区,不显示详细地址,比如:北京市海淀区****
*
* @param address
* @param sensitiveSize 敏感信息长度
* @return
*/
public static String address(String address, int sensitiveSize) {
if (StringUtils.isBlank(address) || address.length() <= sensitiveSize) {
return address;
}
int length = StringUtils.length(address);
return StringUtils.rightPad(StringUtils.left(address, length - sensitiveSize), length, "*");
}
}
定义一个四层的对象引用
@Data
static class EntityVo{
@Desensitized(type = EnumSensitiveType.ENITTY_CLASS)
List<Entity> list;
}
@Data
static class Entity{
@Desensitized(type = EnumSensitiveType.CHINESE_NAME)
String name;
@Desensitized(type = EnumSensitiveType.ENITTY_CLASS)
List<A> list;
}
@Data
static class A {
@Desensitized(type = EnumSensitiveType.CHINESE_NAME)
String name;
@Desensitized(type = EnumSensitiveType.ENITTY_CLASS)
B b;
}
@Data
static class B {
@Desensitized(type = EnumSensitiveType.CHINESE_NAME)
String name;
//A a;
}
public static void main(String[] args) {
A a = new A();
a.setName("慢慢的");
B b = new B();
b.setName("的地方");
a.setB(b);
Entity entity = new Entity();
entity.setName("狭小的");
entity.setList(Arrays.asList(a));
EntityVo entityVo = new EntityVo();
entityVo.setList(Arrays.asList(entity));
DesensitizedUtils.desensitization(entityVo);
System.out.println(GsonUtils.obj2Json(entityVo));
/*A a = new A();
a.setName("慢慢的");
B b = new B();
b.setName("的地方");
a.setB(b);
b.setA(a);
DesensitizedUtils.desensitization(a);*/
//System.out.println(GsonUtils.obj2Json(a));
//System.out.println(a.hashCode());
}
执行结果:
上面utils类的可以写成AOP的方式,但是公司现在不允许再AOP所以使用了Utils方式,大家有改进建议欢迎留言,我会补充上去。
原网址: 访问
创建于: 2021-11-24 10:07:07
目录: default
标签: 无
未标明原创文章均为采集,版权归作者所有,转载无需和我联系,请注明原出处,南摩阿彌陀佛,知识,不只知道,要得到
最新评论