Mybatis安全加解密MySQL数据实战
需求背景
公司为了通过⼀些⾦融安全指标(政策问题)和防⽌数据泄漏,需要对⽤户敏感数据进⾏加密,所以在公司项⽬中所有存储了⽤户信息的数据库都需要进⾏数据加密改造。包括Mysql、redis、mongodb、es、HBase等。
因为在项⽬中是使⽤springboot+mybatis⽅式连接数据库进⾏增删改查,并且项⽬是中途改造数据。所以为了不影响正常业务,打算这次改动尽量不侵⼊到业务代码,加上mybatis开放的各种接⼝,所以就以此进⾏改造数据。手机mysql安装配置教程
本篇⽂章讲述如何在现有项⽬中尽量不侵⼊业务⽅式进⾏Mysql加密数据,最后为了不降低查询性能使⽤了注解,所以最后还是部分侵⼊业务。
Mybatis
Mybatis只能拦截指定类⾥⾯的⽅法:Executor、ParameterHandler、StatementHandler、ResultSetHandler。
Executor:拦截执⾏器⽅法;
ParameterHandler:拦截参数⽅法;
StatementHandler:拦截sql构建⽅法;
ResultSetHandler:拦截查询结果⽅法;
Mybatis提供的接⼝Interceptor
1public interface Interceptor {
2
3  Object intercept(Invocation invocation) throws Throwable;
4
5  default Object plugin(Object target) {
6    return Plugin.wrap(target, this);
7  }
8
9  default void setProperties(Properties properties) {
10    // NOPonesidedlove什么意思
11  }
12}
13复制代码
1- Object intercept():代理对象都会调⽤的⽅法,这⾥可以执⾏⾃定义拦截处理;
2- Object plugin():可以⽤于判断执⾏类型;
3- void setProperties():指定配置⽂件的属性;
4复制代码
⾃定义中除了要实现Interceptor接⼝,还需要添加@Intercepts注解指定拦截对象。@Intercepts注解需配合@Signature注解使⽤
@Intercepts注解可以指定多个@Signature,type指定拦截类,method指定拦截⽅法,args拦截⽅法⾥的参数类型。
2 * @author Clinton Begin
3 */
4@Documented
5@Retention(RetentionPolicy.RUNTIME)
6@Target(ElementType.TYPE)
visual studio2010下载7public @interface Intercepts {
8  Signature[] value();
9}
10
11/**
12 * @author Clinton Begin
13 */
14@Documented
15@Retention(RetentionPolicy.RUNTIME)
16@Target({})
17public @interface Signature {
18  Class<?> type();
19  String method();
20  Class<?>[] args();
21}
22复制代码
案例实战
依据上述的mybatis的使⽤,下⾯就把实战案例代码提供⼀下。
Mybatis⾃定义
在业务代码⾥⽤户信息是以明⽂传递的,所以为了不改动业务代码,那么需要在插⼊或查询数据库数据前先加密,查询结果解密操作。
⾸先搭建⼀个springboot的项⽬,这⾥指定两个mybatis,⼀个拦截请求参数,⼀个拦截响应数据,并把注⼊到spring 容器内。
2 * 对mybatis⼊参进⾏拦截加密
3 * @author zrh
4 */
5@Slf4j
6@Component
7@Intercepts(@Signature(type = ParameterHandler.class, method = "setParameters", args = {PreparedStatement.class})) 8public class MybatisEncryptInterceptor implements Interceptor {
9
10    @Resource
11    batis.Interceptor.MybatisCryptHandler handler;
12
13    @Override
14    public Object intercept (Invocation invocation) {
15        return invocation;
16    }
17
18    @SneakyThrows
19    @Override
20    public Object plugin (Object target) {
21        if (target instanceof ParameterHandler) {
22            // 对请求参数进⾏加密操作
23            handler.parameterEncrypt((ParameterHandler) target);
24        }
25        return target;
26    }
27
28    @Override
29    public void setProperties (Properties properties) {
30    }
31}
32复制代码
注意:ResultSetHandler对象对增删改⽅法没有拦截,需要增加Executor对象;
1/**
2 * 对mybatis查询结果进⾏拦截解密,并对请求参数进⾏拦截解密还原操作
3 * @author zrh
4 */
5@Slf4j
6@Component
7@Intercepts({
8        @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class}),
9        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
10})
11public class MybatisDecryptInterceptor implements Interceptor {
12
13    @Resource
14    private MybatisCryptHandler handler;
15
16    @Override
17    public Object intercept (Invocation invocation) throws Exception {
18        // 获取执⾏mysql执⾏结果
19        Object result = invocation.proceed();
20        if (Target() instanceof Executor) {
21            // 对增删改操作⽅法的请求参数进⾏解密还原操作
22            Args());
23            return result;
24        }
25        // 对查询⽅法的请求参数进⾏解密还原操作
26        Target());
27        // 对查询结果进⾏解密
28        sultDecrypt(result);
28        sultDecrypt(result);
29    }
30
31    @Override
32    public Object plugin (Object target) {
33        return Plugin.wrap(target, this);
34    }
35
36    @Override
37    public void setProperties (Properties properties) {
38    }
39
40    /**
41    * 对请求参数进⾏解密还原操作
42    * @param target
43    */
44    private void checkEncryptByQuery (Object target) {
45        try {
46            final Class<?> targetClass = Class();
47            final Field parameterHandlerFiled = DeclaredField("parameterHandler");
48            parameterHandlerFiled.setAccessible(true);
49            final Object parameterHandler = (target);
50            final Class<?> parameterHandlerClass = Class();
51            final Field parameterObjectField = DeclaredField("parameterObject");
int128vs编译器支持52            parameterObjectField.setAccessible(true);
53            final Object parameterObject = (parameterHandler);
54            handler.decryptFieldHandler(parameterObject);
55        } catch (Exception e) {
56            ("对请求参数进⾏解密还原操作异常:", e);
57        }
58    }
59
60    /**
61    * 对请求参数进⾏解密还原操作
62    * @param args
63    */
64    private void checkEncryptByUpdate (Object[] args) {
65        try {
66            Arrays.stream(args).forEach(handler::decryptFieldHandler);
67        } catch (Exception e) {
68            ("对请求参数进⾏解密还原操作异常:", e);
69        }
70    }
71}
72复制代码
在上述中,除了对⼊参进⾏加密和查询结果解密操作外,还多了⼀步对请求参数进⾏解密还原操作。
这是因为对请求参数进⾏加密操作时改动的是原对象,如果不还原解密数据,这个对象如果在后续还有其他操作,那就会使⽤密⽂,导致数据紊乱。
这⾥其实想过不改动原对象,⽽是把原请求对象克隆⼀份,在克隆对象上进⾏加密,然后在去查询数据库。可惜可能是⾃⼰对mybatis 不够熟悉吧,试了很久也不能把mybatis内的原对象替换为克隆对象,所以才就想了这个还原解密参数的⽅式。
如果对请求参数对象和查询结果对象⾥的所有字段都进⾏加解密,那上述配置就基本完成。但在本次安全加解密需求中只针对指定字段
(如⼿机号和真实姓名),现在这种全量字段加解密就不⾏,⽽且性能也低,毕竟加解密是很耗费服务器CPU运算资源的。
所以需要增加注解,在指定对象的属性字段才进⾏加解密。
1/**
2 * <p>作⽤于类:标识当前实体需要进⾏结果解密操作.
3 * <p>作⽤于字段:标识当前实体的字段需要进⾏加解密操作.
4 * <p>作⽤于⽅法:标识当前mapper⽅法会被切⾯进⾏拦截,并进⾏数据的加解密操作.
5 * <p>注意:如果作⽤于字段,那当前类必须先标注该注解,因为会优先判断类是否需要加解密,然后在判断字段是否需要加解密,否则只作⽤于字段不会起效
6 *
7 * @author zrhui培训学校
8 * @date 2022/1/4
9 */
10@Documented
11@Inherited
12@Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
13@Retention(RetentionPolicy.RUNTIME)
14public @interface Crypt {
15    /**
16    * 默认字段需要解密
17    */
18    boolean decrypt () default true;
19    /**
20    * 默认字段需要加密
21    */
22    boolean encrypt () default true;
23    /**
24    * 字段为对象时有⽤,默认当前对象不需要进⾏加解密
25    */
26    boolean subObject () default false;
27    /**
28    * 需要进⾏加密的字段列下标html5 canvas时钟
29    */
30    int[] encryptParamIndex () default {};
31}
32复制代码
其注解使⽤⽅式如下:

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。