项目场景:
提示:这里简述项目相关背景:
例如:Mybatis加密解密查询操作(sql前),where要传入加密后的字段时遇到的问题
问题描述
提示:这里描述项目中遇到的问题:
例如:数据传输过程中数据不时出现丢失的情况,偶尔会丢失一部分数据
APP 中接收数据代码:
public Object intercept(Invocation invocation) throws Throwable {
ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();
// 获取传入的参数对象
Object parameterObject = parameterHandler.getParameterObject();
if (parameterObject instanceof Map) {
Map<?, ?> paramMap = (Map<?, ?>) parameterObject;
for (Map.Entry<?, ?> entry : paramMap.entrySet()) {
Object key = entry.getKey();
Object value = entry.getValue();
// 对于带有 @Sensitive 注解的字段,进行加密处理
if (value != null && !value.toString().isEmpty()) { // 判空和空字符串检查
// 反射检查该字段是否标记了 @Sensitive 注解
if (isSensitiveField(value)) {
String encryptedValue = SensitiveDataUtils.encrypt(value.toString(), SM4_KEY);
((Map<Object, Object>) paramMap).put(key, encryptedValue);
}
}
}
} else {
// 处理非 Map 类型的参数(如单个对象查询)
encryptSensitiveFields(parameterObject);
}
// 执行原始逻辑
return invocation.proceed();
}
这个方法有问题啊
原因分析:
提示:这里填写问题的分析:
是的,这个 intercept 方法存在几个潜在问题,可能导致在处理加密字段时出现错误。以下是详细的分析和改进建议:
- parameterObject 为 Map 类型时的处理
在你当前的代码中,如果 parameterObject 是 Map 类型,会迭代其中的 key-value 对并检查每个 value 是否标记了 @Sensitive 注解,进而进行加密处理。这里有几个潜在问题:
value 可能是复杂对象:目前的判断条件是 if (value != null && !value.toString().isEmpty()),但是 value 可能是一个复杂对象(例如 User 对象),而不是一个简单的 String 类型。在这种情况下,直接使用 value.toString() 可能并不能反映实际字段的内容。
isSensitiveField 反射问题:你尝试通过反射检查 value 是否包含 @Sensitive 注解,但是 value 很可能是一个普通的 Java 对象(如 AdminUserDO),直接传给 isSensitiveField 可能会导致反射错误。
-
parameterObject 不是 Map 类型时的处理
如果 parameterObject 不是 Map 类型,你使用 encryptSensitiveFields(parameterObject) 来处理加密。该方法会检查对象中的字段并加密被 @Sensitive 注解标记的字段。问题在于,你并没有考虑 parameterObject 可能是一个嵌套对象,或者该对象的类型可能没有适当的 getter 方法,导致反射出错。 -
改进建议
我们需要确保在处理 Map 类型和非 Map 类型的参数时,都能正确处理嵌套对象和字段加密。
解决方案:
提示:这里填写该问题的具体解决方案:
package cn.iocoder.yudao.framework.mybatis.core.handler;
import cn.iocoder.yudao.framework.annotation.Sensitive;
import cn.iocoder.yudao.framework.mybatis.core.util.SensitiveDataUtils;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.plugin.*;
import java.lang.reflect.Field;
import java.sql.PreparedStatement;
import java.util.Map;
import static cn.iocoder.yudao.framework.mybatis.core.util.SensitiveDataUtils.SM4_KEY;
@Intercepts({
@Signature(type = ParameterHandler.class, method = "setParameters", args = {PreparedStatement.class})
})
public class SensitiveDataEncryptInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();
// 获取传入的参数对象
Object parameterObject = parameterHandler.getParameterObject();
// 如果是 Map 类型的参数
if (parameterObject instanceof Map) {
Map<?, ?> paramMap = (Map<?, ?>) parameterObject;
for (Map.Entry<?, ?> entry : paramMap.entrySet()) {
Object key = entry.getKey();
Object value = entry.getValue();
// 判断 value 是否为带有 @Sensitive 注解的字段,进行加密处理
if (value != null) {
// 对 value 进行加密
String encryptedValue = encryptSensitiveValue(value);
// 如果字段需要加密,替换 Map 中的值
if (encryptedValue != null) {
// 强制转换为 Map<Object, Object> 来避免类型问题
((Map<Object, Object>) paramMap).put(key, encryptedValue);
}
}
}
} else {
// 如果不是 Map 类型,处理对象的字段加密
encryptSensitiveFields(parameterObject);
}
// 执行原始逻辑
return invocation.proceed();
}
private String encryptSensitiveValue(Object value) throws Exception {
if (value instanceof String && isSensitiveField(value)) {
// 对象字段加密处理
return SensitiveDataUtils.encrypt(value.toString(), SM4_KEY);
}
return null;
}
private boolean isSensitiveField(Object value) {
// 判断该对象字段是否带有 @Sensitive 注解
Class<?> clazz = value.getClass();
for (Field field : clazz.getDeclaredFields()) {
if (field.isAnnotationPresent(Sensitive.class)) {
return true; // 该字段带有 @Sensitive 注解
}
}
return false;
}
private void encryptSensitiveFields(Object parameterObject) throws Exception {
// 遍历对象的字段,检查是否带有 @Sensitive 注解
Field[] fields = parameterObject.getClass().getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(Sensitive.class)) {
field.setAccessible(true);
Object value = field.get(parameterObject);
if (value != null) {
String encryptedValue = SensitiveDataUtils.encrypt(value.toString(), SM4_KEY);
field.set(parameterObject, encryptedValue);
}
}
}
}
}
主要改进点:
encryptSensitiveValue 方法:
这个方法首先检查传入的 value 是否是需要加密的字段,如果是,则进行加密。
通过调用 isSensitiveField(value) 来判断是否需要加密,而不直接依赖 value.toString()。
isSensitiveField 方法:
通过反射检查 value 对象是否包含带有 @Sensitive 注解的字段。之前可能存在错误的反射逻辑,这里是基于对象字段的反射检查。
encryptSensitiveFields 方法:
遍历 parameterObject 的字段,并加密带有 @Sensitive 注解的字段。处理对象字段时,直接通过反射获取字段并进行加密。
其他注意事项:
确保 SensitiveDataUtils.encrypt 方法能够正确处理所有类型的 value,尤其是对于非 String 类型的对象,可能需要进一步优化加密处理。
如果 parameterObject 是一个复杂的对象,确保该对象的所有字段都可以通过反射访问(即字段为 public 或者使用了 setAccessible(true))。
这样修改后,应该能够有效避免反射错误并正确处理加密逻辑。