上一篇,我用的自定义注解+AOP的方式,实现敏感字段的加解密,这一篇换个方案,这个方案相比一个方案,节省了一部分的性能开销
第一步:新建自定义注解
/**
* 敏感信息类注解
*/
@Inherited
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface EnableSensitive {
boolean value() default true;
}
/**
* 敏感字段注解
*/
@Inherited
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface SensitiveFiled {
String value() default "";
}
第二步:创建加密实现类
@Component
@Intercepts({
//@Signature注解定义拦截器的实际类型
@Signature(type = ParameterHandler.class, method = "setParameters", args = PreparedStatement.class)
})
public class EncryptInterceptor implements Interceptor {
@Resource
private SensitiveInfoService sensitiveInfoService;
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 取出请求参数
ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();
Object parameterObject = parameterHandler.getParameterObject();
// 如果参数不为空,并且是MapperMethod.ParamMap类型
if (parameterObject instanceof MapperMethod.ParamMap) {
Map<String, ?> paramMap = (MapperMethod.ParamMap) parameterObject;
// 遍历请求参数,对敏感信息字段加密
paramMap.forEach((k, v) -> {
// 参数不为空,并且不是mybatis默认参数名
if (null != paramMap.get(k)) {
this.encrypt(paramMap.get(k));
}
});
} else if (null != parameterObject) {
this.encrypt(parameterObject);
}
return invocation.proceed();
}
private void encrypt(Object param) {
EnableSensitive enableSensitive = AnnotationUtils.findAnnotation(param.getClass(), EnableSensitive.class);
if (null != enableSensitive && enableSensitive.value()) {
// 对敏感信息类中的敏感信息字段加密
sensitiveInfoService.encrypt(param);
}
}
/**
* 加入此拦截器到拦截器链
*/
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
}
代码注释:
这段代码的功能是实现一个名为EncryptInterceptor的类,用作MyBatis的拦截器,主要对数据库操作中的敏感信息字段进行加密处理。具体功能如下:
- 依赖注入:通过@Resource注解,注入SensitiveInfoService服务,用于敏感信息的加密处理。
- 拦截器实现:实现Interceptor接口,对指定的方法进行拦截。此处拦截的是ParameterHandler类的setParameters方法,该方法负责设置PreparedStatement的参数。
- 参数获取与判断:在intercept方法中,获取ParameterHandler的参数对象。如果参数对象不为空且为MapperMethod.ParamMap类型(即Mapper接口方法的参数被封装成Map形式),则进行后续处理。
- 获取ParameterHandler的参数对象如果不是MapperMethod.ParamMap类型,则对整个对象加密
- 敏感信息加密:遍历参数Map,对每个非空参数值,,encrypt方法用于判断参数类上是否有@EnableSensitive注解,若有则调用sensitiveInfoService进行加密处理。
- 执行流程控制:完成加密处理后,通过invocation.proceed()继续执行被拦截的方法,即ParameterHandler的setParameters方法。
- 插件机制:通过plugin方法,利用MyBatis的插件机制,将此拦截器加入到MyBatis的拦截器链中,从而对指定的目标对象进行增强处理。
综上所述,EncryptInterceptor的主要作用是在MyBatis执行SQL之前,拦截并加密Mapper方法中的敏感信息字段,确保敏感数据的安全性。
第三步:创建解密实现类
@Component
@Intercepts({
@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})
})
public class DecryptInterceptor implements Interceptor {
@Resource
private SensitiveInfoService sensitiveInfoService;
@Override
public Object intercept(Invocation invocation) throws Throwable {
//取出查询的结果
Object resultObject = invocation.proceed();
if (null != resultObject) {
// 校验是否需要解密,并对敏感信息进行解密
sensitiveInfoService.decrypt(resultObject);
}
return resultObject;
}
/**
* 加入此拦截器到拦截器链
*/
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
}
代码注释:
这段代码定义了一个名为DecryptInterceptor的拦截器类,用于在查询数据库后对结果进行解密。该拦截器通过@Component和@Intercepts注解标记,指定了它将拦截ResultSetHandler类中的handleResultSets方法,该方法接受一个Statement类参数。
主要功能包括:
- 拦截查询结果:在intercept方法中,拦截器取出查询的结果对象。
- 解密敏感信息:利用注入的SensitiveInfoService服务,检查查询结果是否需要解密,并对包含的敏感信息执行解密操作。
- 返回解密后的结果:最后,将处理后的结果返回。
此外,plugin方法用于将此拦截器加入到拦截器链中,使用Plugin.wrap方法包装目标对象。
具体的加解密service
public interface SensitiveInfoService {
/**
* 加密
*
* @param param 待加密字段外层对象
*/
<T> T encrypt(T param);
/**
* 解密
*
* @param result 待解密字段外层对象
*/
<T> T decrypt(T result);
}
加解密impl
@Service
public class SensitiveInfoServiceImpl implements SensitiveInfoService {
public static final Logger log = LoggerFactory.getLogger(SensitiveInfoServiceImpl.class);
/**
* 密钥
*/
private static final String SM4_KEY = "your_key";
/**
* 加密过标识
*/
public static final String ENCRYPTED_PREFIX = "{SSI}";
@Override
public <T> T encrypt(T param) {
// 校验是否需要加密,并返回需要加密字段
List<Field> fields = this.checkAndGetFields(param);
// 加密字段不为空,加密
if (!CollectionUtils.isEmpty(fields)) {
fields.forEach(field -> {
// 加密
this.encrypt(field, param);
});
}
return param;
}
/**
* 加密
*/
private void encrypt(Field field, Object o) {
// 如果是集合,则遍历出实体,逐个加密
if (o instanceof Collection) {
((Collection<?>) o).forEach(e -> {
this.encryptObj(field, e);
});
} else {
this.encryptObj(field, o);
}
}
/**
* 加密
*/
private void encryptObj(Field field, Object o) {
try {
// 访问private变量的变量值
field.setAccessible(true);
// 字段值
Object v = field.get(o);
// 只对String类型来加密
if (v instanceof String) {
String value = (String) v;
// 如果没加密过 (不以{SSI}开头,或者没加密)
if (StringUtils.isNotEmpty(value) && null == this.decryptStr(value)) {
// 加密并重新赋值
field.set(o, ENCRYPTED_PREFIX + SM4Util.encryptEcb(SM4_KEY, value));
}
}
} catch (Exception e) {
log.error("字段加密失败,类={}, 字段={}", o.getClass().getName(), field.getName(), e);
}
}
@Override
public <T> T decrypt(T result) {
// 校验是否需要解密,并返回需要解密字段
List<Field> fields = this.checkAndGetFields(result);
// 解密字段不为空,解密
if (!CollectionUtils.isEmpty(fields)) {
fields.forEach(field -> {
// 解密
this.decrypt(field, result);
});
}
return result;
}
/**
* 解密
*/
private void decrypt(Field field, Object o) {
// 如果是集合,则遍历出实体,逐个解密
if (o instanceof Collection) {
((Collection<?>) o).forEach(e -> {
this.decryptObj(field, e);
});
} else {
this.decryptObj(field, o);
}
}
/**
* 对象中字段解密
*/
private void decryptObj(Field field, Object o) {
try {
// 访问private变量的变量值
field.setAccessible(true);
// 字段值
Object value = field.get(o);
// 只对String类型来解密
if (value instanceof String) {
// 解密
String decrypt = this.decryptStr((String) value);
// 解密成功,重新赋值
if(StringUtils.isNotEmpty(decrypt)) {
field.set(o, decrypt);
}
}
} catch (Exception e) {
log.error("字段解密失败,类={}, 字段={}", o.getClass().getName(), field.getName(), e);
}
}
/**
* 加密信息解密
*/
private String decryptStr(String value) {
if (StringUtils.isNotEmpty(value) && value.startsWith(ENCRYPTED_PREFIX)) {
try {
return SM4Util.decryptEcb(SM4_KEY, value.substring(ENCRYPTED_PREFIX.length()));
} catch (Exception e) {
log.error("字段解密失败,value={}", value, e);
}
}
return null;
}
/**
* 校验是否需要加解密,并返回需要加解密字段
*/
private <T> List<Field> checkAndGetFields(T t) {
// 数据为空直接返回
if (null == t) {
return null;
}
if (t instanceof Collection) {
// 如果是集合,返回集合中元素的类中需要加解密的敏感信息字段
Collection<?> rc = (Collection<?>) t;
if (!CollectionUtils.isEmpty(rc)) {
Object next = rc.iterator().next();
if (null != next) {
return ReflectUtils.getAllField(next.getClass(), SensitiveFiled.class);
}
}
} else {
// 返回需要加解密的敏感信息字段
return ReflectUtils.getAllField(t.getClass(), SensitiveFiled.class);
}
return null;
}
}
示例: