项目中如果所有字段都脱敏,可以使用jackson。aop可以用redis做开关,也可以调整切面精确范围,将脱敏的包隔开。但这2种都无法解决导出时数据脱敏,导出excel只能手动调用。例如:
1.aop
启动类加@EnableAspectJAutoProxy
自定义注解,在实体类中使用表示被脱敏字段
建立aop切面类
//这里是上面的maskdata方法的复杂版,因为项目中的对象都是嵌套包裹的
private void desensitizeFields( Object obj) throws IllegalAccessException {
if (obj == null) return;
// 如果对象是一个Map类型
if (obj instanceof Map<?, ?>) {
Map<?, ?> map = (Map<?, ?>) obj;
for (Map.Entry<?, ?> entry : map.entrySet()) {
Object value = entry.getValue();
if (value != null) {
// 递归处理Map中的对象
desensitizeFields(value);
}
}
return;
}
// 如果对象是一个List类型
if (obj instanceof List<?>) {
List<?> list = (List<?>) obj;
for (Object item : list) {
if (item != null) {
// 递归处理List中的每个对象
desensitizeFields(item);
}
}
return;
}
// 处理对象的字段
Class<?> clazz = obj.getClass();
List<Field> fields = new ArrayList<>(Arrays.asList(clazz.getDeclaredFields()));
clazz = clazz.getSuperclass();
if (clazz!=null){
fields.addAll(Arrays.asList(clazz.getDeclaredFields()));
}
for (Field field : fields) {
if (Modifier.isFinal(field.getModifiers())) {
continue;
}
field.setAccessible(true);
Object valueOrigin = field.get(obj);
if (valueOrigin!=null&&field.isAnnotationPresent(SensitiveData.class)) {
SensitiveData annotation = field.getAnnotation(SensitiveData.class);
// 注意:这里需要设置field为可访问
// 根据字段类型执行不同的脱敏逻辑
SensitiveTypeEnum sensitiveTypeEnum = annotation.value();
String value = valueOrigin.toString();
switch (sensitiveTypeEnum) {
case COMMON:
value = MsgDesensitizedUtil.commonStr(value);
break;
case ID_CARD:
value = MsgDesensitizedUtil.idCardNum(value);
break;
case PHONE_NUMBER:
value = MsgDesensitizedUtil.mobilePhone(value);
break;
case EMAIL:
value = MsgDesensitizedUtil.email(value);
break;
default:
throw new RuntimeException("未知脱敏类型");
}
field.set(obj, value);
} else if (valueOrigin != null && !isPrimitiveOrWrapper(field.getType())) {
// 如果字段是一个对象(非基本类型),递归处理
desensitizeFields(valueOrigin);
}
}
}
private boolean isPrimitiveOrWrapper(Class<?> clazz) {
return clazz.isPrimitive() ||
clazz.equals(String.class) ||
clazz.equals(Boolean.class) ||
clazz.equals(Integer.class) ||
clazz.equals(Character.class) ||
clazz.equals(Byte.class) ||
clazz.equals(Short.class) ||
clazz.equals(Double.class) ||
clazz.equals(Long.class) ||
clazz.equals(Float.class);
}
/**
* 共通脱敏
*
* @param commonStr
* @return
*/
public static String commonStr(String commonStr) {
if (StrUtil.isBlank(commonStr)) {
return "";
} else if (commonStr.length() == 11) {
return StrUtil.hide(commonStr, 3, commonStr.length() - 4);
} else if (commonStr.length() == 15 || commonStr.length() == 18) {
return StrUtil.hide(commonStr, 2, commonStr.length() - 3);
} else if (commonStr.length() == 1) {
return StrUtil.hide(commonStr, 0, commonStr.length());
}else if (commonStr.length() <= 3) {
return StrUtil.hide(commonStr, 1, commonStr.length());
} else if (commonStr.length() > 3 && commonStr.length() <= 6) {
return StrUtil.hide(commonStr, 1, commonStr.length() - 1);
} else if (commonStr.length() > 6 && commonStr.length() <= 10) {
return StrUtil.hide(commonStr, 2, commonStr.length() - 2);
} else if (commonStr.length() > 10 && commonStr.length() <= 16) {
return StrUtil.hide(commonStr, 3, commonStr.length() - 4);
} else if (commonStr.length() > 16) {
return StrUtil.hide(commonStr, 4, commonStr.length() - 5);
} else {
return StrUtil.hide(commonStr, 1, commonStr.length());
}
可能这里gpt会建议你用@Pointcut("execution(public * com.xx.aop..*.get*(..))")这种方式拦截,这种我试了,拦截不住。猜测在mvc返回的时候,已经不被aop拦住了,除非手动调用。并且get方式还要user成为bean,不值当。直接拦截controller包吧。
2.Jackson
序列化类
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import java.io.IOException;
public class DesensitizeSerializer extends JsonSerializer<String> implements ContextualSerializer {
private SensitiveType type;
private int startInclude;
private int endExclude;
public DesensitizeSerializer() {
this.type = SensitiveType.COMMON;
}
public DesensitizeSerializer(SensitiveType type) {
this.type = type;
}
@Override
public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException, IOException {
switch (type) {
case COMMON:
gen.writeString(MsgDesensitizedUtil.commonStr(value));
break;
case ID_CARD:
gen.writeString(MsgDesensitizedUtil.idCardNum(value));
break;
case PHONE_NUMBER:
gen.writeString(MsgDesensitizedUtil.mobilePhone(value));
break;
case EMAIL:
gen.writeString(MsgDesensitizedUtil.email(value));
break;
default:
throw new RuntimeException("未知脱敏类型");
}
}
@Override
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) {
if (property != null) {
SensitiveData annotation = property.getAnnotation(SensitiveData.class);
if (annotation != null) {
this.type = annotation.value();
}
}
return this;
}
}
针对多种类型的脱敏枚举类
在实体中添加就行了,不需要把自定义序列化加载到SimpleModule里