需求背景
- 公司为了通过一些金融安全指标(政策问题)和防止数据泄漏,需要对用户敏感数据进行加密,所以在公司项目中所有存储了用户信息的数据库都需要进行数据加密改造。包括Mysql、redis、mongodb、es、HBase等。
- 因为在项目中是使用springboot+mybatis方式连接数据库进行增删改查,并且项目是中途改造数据。所以为了不影响正常业务,打算这次改动尽量不侵入到业务代码,加上mybatis开放的各种拦截器接口,所以就以此进行改造数据。
- 本篇文章讲述如何在现有项目中尽量不侵入业务方式进行Mysql加密数据,最后为了不降低查询性能使用了注解,所以最后还是部分侵入业务。
Mybatis拦截器
-
Mybatis只能拦截指定类里面的方法:
Executor、ParameterHandler、StatementHandler、ResultSetHandler。
-
Executor
:拦截执行器方法; -
ParameterHandler
:拦截参数方法; -
StatementHandler
:拦截sql构建方法; -
ResultSetHandler
:拦截查询结果方法;
-
- Mybatis提供的拦截器接口Interceptor
public interface Interceptor {
Object intercept(Invocation invocation) throws Throwable;
default Object plugin(Object target) {
return Plugin.wrap(target, this);
}
default void setProperties(Properties properties) {
// NOP
}
}
复制代码
- Object intercept():代理对象都会调用的方法,这里可以执行自定义拦截处理;
- Object plugin():可以用于判断拦截器执行类型;
- void setProperties():指定配置文件的属性;
复制代码
- 自定义拦截器中除了要实现Interceptor接口,还需要添加@Intercepts注解指定拦截对象。@Intercepts注解需配合@Signature注解使用
- @Intercepts注解可以指定多个@Signature,type指定拦截类,method指定拦截方法,args拦截方法里的参数类型。
/**
* @author Clinton Begin
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Intercepts {
Signature[] value();
}
/**
* @author Clinton Begin
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Signature {
Class<?> type();
String method();
Class<?>[] args();
}
复制代码
案例实战
- 依据上述的mybatis拦截器的使用,下面就把实战案例代码提供一下。
Mybatis自定义拦截器
- 在业务代码里用户信息是以明文传递的,所以为了不改动业务代码,那么需要拦截器在插入或查询数据库数据前先加密,查询结果解密操作。
- 首先搭建一个springboot的项目,这里指定两个mybatis拦截器,一个拦截请求参数,一个拦截响应数据,并把拦截器注入到spring容器内。
/**
* 对mybatis入参进行拦截加密
* @author zrh
*/
@Slf4j
@Component
@Intercepts(@Signature(type = ParameterHandler.class, method = "setParameters", args = {PreparedStatement.class}))
public class MybatisEncryptInterceptor implements Interceptor {
@Resource
private com.mysql.web.mybatis.Interceptor.MybatisCryptHandler handler;
@Override
public Object intercept (Invocation invocation) {
return invocation;
}
@SneakyThrows
@Override
public Object plugin (Object target) {
if (target instanceof ParameterHandler) {
// 对请求参数进行加密操作
handler.parameterEncrypt((ParameterHandler) target);
}
return target;
}
@Override
public void setProperties (Properties properties) {
}
}
复制代码
- 注意:ResultSetHandler对象对增删改方法没有拦截,需要增加Executor对象;
/**
* 对mybatis查询结果进行拦截解密,并对请求参数进行拦截解密还原操作
* @author zrh
*/
@Slf4j
@Component
@Intercepts({
@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class}),
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
})
public class MybatisDecryptInterceptor implements Interceptor {
@Resource
private MybatisCryptHandler handler;
@Override
public Object intercept (Invocation invocation) throws Exception {
// 获取执行mysql执行结果
Object result = invocation.proceed();
if (invocation.getTarget() instanceof Executor) {
// 对增删改操作方法的请求参数进行解密还原操作
checkEncryptByUpdate(invocation.getArgs());
return result;
}
// 对查询方法的请求参数进行解密还原操作
checkEncryptByQuery(invocation.getTarget());
// 对查询结果进行解密
return handler.resultDecrypt(result);
}
@Override
public Object plugin (Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties (Properties properties) {
}
/**
* 对请求参数进行解密还原操作
* @param target
*/
private void checkEncryptByQuery (Object target) {
try {
final Class<?> targetClass = target.getClass();
final Field parameterHandlerFiled = targetClass.getDeclaredField("parameterHandler");
parameterHandlerFiled.setAccessible(true);
final Object parameterHandler = parameterHandlerFiled.get(target);
final Class<?> parameterHandlerClass = parameterHandler.getClass();
final Field parameterObjectField = parameterHandlerClass.getDeclaredField("parameterObject");
parame
版权声明:本文为superjava_原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。