【Mybatis】基于Mybatis拦截器+注解,实现敏感数据自动加解密

一、背景

我司最近在做等保,要求数据库里面的手机号、微信号、身份证号等等这些在数据库中都不能是明文,所以需要把数据库中的涉及到这些的加密保存,但是查询和保存的接口传参不受影响,之前业务给前端返回是什么样子依然保持不变,这样前端不受影响。

二、实现方式的思考

1.可以直接代码修改,代码中涉及的敏感数据接口在查询和插入、更新时进行加解密。缺点是工作量大,代码侵入多。

2.在mybatis中进行统一拦截,上层业务调用不需要再考虑敏感数据的加解密问题,可以考虑配置文件中配置需要加解密的表和字段,或者注解的方式。

也看了一些博客,最终采用了第二种方式,通过拦截器+注解的方式,实现敏感数据自动加解密

三、mybatis插件的原理:


这里也可以自行查询,资料也很多。

Mybatis的插件,是采用责任链机制,通过JDK动态代理来实现的。默认情况下,Mybatis允许使用插件来拦截四个对象:

Executor:执行CURD操作;
StatementHandler:处理sql语句预编译,设置参数等相关工作;
ParameterHandler:设置预编译参数用的;
ResultSetHandler:处理结果集。

四、代码实现

设置参数时对参数中含有敏感字段的数据进行加密

对查询返回的结果进行解密处理

按照对Executor和ResultSetHandler进行切入,这里也参考了一些博主,大部分是按照对ParameterHandlerResultSetHandler进行切入,但在实践中发现ParameterHandler在某些场景支持(比如传参对象中有字段为list或者mapper中是list传入的)的不好,但我司项目中是有很多这种情况的。后面通过跟源码发现是在改值之前分页插件已经赋值了查询的参数,所以后面对list改值之后并未生效。

实体类中有加解密字段时使用@SensitiveData注解,单个需加解密的字段使用@SensitiveField注解

注解:

  1. SensitiveData注解:用在实体类上,表示此类有些字段需要加密,需要结合@SensitiveField一起使用
  2. SensitiveField注解:用在类的字段上或者方法的参数上,表示该字段或参数需要加密

1.SensitiveData注解

import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Created on 2024/1/9
 * * 该注解定义在类上
 *  * 插件通过扫描类对象是否包含这个注解来决定是否继续扫描其中的字段注解
 *  * 这个注解要配合SensitiveField注解
 * @author www
 * @version V1.0
 * @apiNote
 */
@Inherited
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface SensitiveData {

}

2.SensitiveField注解

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Created on 2024/1/9
 * 该注解有两种使用方式
 * ①:配合@SensitiveData加在类中的字段上
 * ②:直接在Mapper中的方法参数上使用
 * @author www
 * @version V1.0
 * @apiNote
 */
@Documented
@Inherited
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface SensitiveField {
}

插件实现代码:

1.入参处理


import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.AnnotationUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;


@Intercepts(
        {
                @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
                @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
                @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
        }
)
@Component
public class BarExecuteInterceptor implements Interceptor {
    private static final Logger log = LoggerFactory.getLogger(BarExecuteInterceptor.class);
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
    
        try {
            if (invocation.getTarget() instanceof Executor) {
                MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
                Object parameterObject = invocation.getArgs()[1];
                if (parameterObject != null) {
                    //批量保存
                    if (parameterObject instanceof Map) {
                        Map map = (Map) parameterObject;
                        for (Object key : map.keySet()) {
                            Object value = map.get(key);
                            if (value != null){
                                if (value instanceof List) {
                                    List list = (List) value;
                                    for (Object item : list) {
                                        //list<phone>
                                        if (item instanceof String) {
                                            if (isMethodParameterAnnotated(mappedStatement, (String) key)) {
                                                String encryptedItem = EncryptUtil.encryptValue((String) item);
                                                //当list中只有一个值时可能是singletonList,
                                                if (list != null && list.size() == 1){
                                                    list = new ArrayList<>(list);
                                                }
                                                // 替换原有值
                                                list.set(list.indexOf(item), encryptedItem);
                                            }
                                        } else {
                                            //OBJECT 类型 走这个方法
                                            // 递归处理其他类型的对象
                                            processSensitiveFields(mappedStatement, item);
                                        }
                                    }
                                    map.put(key,list);
                                } else if (value instanceof String) {
                                    if (isMethodParameterAnnotated(mappedStatement, (String) key)) {
                                        String encryptedItem = EncryptUtil.encryptValue((String) value);
                                        map.put(key, encryptedItem);
                                    }
                                } else if (value instanceof Map) {
                                    // Map入参情况,这种不用处理,实际map的也没办法加加密的注解
                                    continue;
                                } else {
                                    processSensitiveFields(mappedStatement, value);
                                }
                            }
                        }
                    } else if (parameterObject instanceof List) {
                        // 检查是否为TestForm实例或者包含TestForm实例的列表
                        // 如果参数是列表,检查列表中的每个元素是否为TestForm实例
                        List<?> parameterList = (List<?>) parameterObject;
                        for (Object param : parameterList) {
                            processSensitiveFields(mappedStatement, param);
                        }
                    } else if (parameterObject instanceof String) {
                        if (isMethodSingleParameterAnnotated(mappedStatement)) {
                            String encryptedItem = EncryptUtil.encryptValue((String) parameterObject);
                            invocation.getArgs()[1] = encryptedItem;
                        }
                    } else {
                        //通用的保存单条数据时,对象查询
                        processSensitiveFields(mappedStatement, parameterObject);
                    }
                }
                // 继续执行原始方法
                return invocation.proceed();
            }
        } catch (Exception e) {
            log.error("加密参数处理发生异常:{}",e.getMessage(),e);
        }
        // 如果不是Executor&#x
MyBatis拦截器和自定义注解MyBatis框架中的两个重要特性。下面我会分别解释它们的作用和用法。 MyBatis拦截器是一种机制,可以在执行SQL语句的过程中对其进行拦截和修改。它提供了一种方便的方式来扩展和自定义MyBatis的功能。拦截器可以在SQL语句执行前后、参数设置前后、结果集处理前后等关键点进行拦截,并对其进行修改或增强。 要实现一个MyBatis拦截器,你需要实现`Interceptor`接口,并重写其中的方法。其中最重要的方法是`intercept`,它接收一个`Invocation`对象作为参数,通过该对象你可以获取到当前执行的SQL语句、参数等信息,并可以对其进行修改。另外还有`plugin`方法和`setProperties`方法用于对拦截器进行初始化。 自定义注解是一种用于标记和配置特定功能的注解。在MyBatis中,你可以使用自定义注解来配置一些特殊的功能,比如动态SQL的条件判断、结果集映射等。通过自定义注解,你可以将一些常用的功能封装成注解,并在需要时直接使用。 要使用自定义注解,你需要先定义一个注解,并在相应的地方使用该注解。然后通过MyBatis的配置文件或者Java代码进行配置,告诉MyBatis如何处理这些注解。在MyBatis的执行过程中,它会根据注解的配置来动态生成相应的SQL语句或者进行特定的处理。 总结一下,MyBatis拦截器和自定义注解MyBatis框架中的两个重要特性。拦截器可以对SQL语句进行拦截和修改,自定义注解可以用于配置一些特殊功能。它们都提供了一种扩展和自定义MyBatis功能的方式。如果你有具体的问题或者需要更详细的示例代码,欢迎继续提问!
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值