mybatis数据加解密处理方案

1.背景

  为了防止数据库的用户数据安全,所以需要对用户数据进行加密,具体为插入数据进行加密,查询数据自动解密。

2.方案

  查询相关文档后,发现mybatis有2种方案可以处理:

   a.使用typeHandler

   b.使用intercept

   经过对批量数据执行后,发现千、万、百万级别数据拦截器相对更快一些。

3.具体实现

  3.1 intercept

   a.注解

   EncryptDecryptData 该注解用于标记拦截器适用的DBEntity

@Inherited
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptDecryptData {
}
EncryptDecryptData

  EncryptDecryptField 该注解用于标记拦截器用于加密的字段

@Inherited
@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptDecryptField {
}
EncryptDecryptField

  b.加密方法

import java.lang.reflect.Field;
import java.util.Objects;

public class  EncryptDecrypt {

    private final static String key = "asffqqas";
    /**
     * 加密
     *
     * @param declaredFields paramsObject所声明的字段
     * @param paramsObject   mapper中paramsType的实例
     * @return T
     * @throws IllegalAccessException 字段不可访问异常
     */
    public static <T> T encrypt(Field[] declaredFields, T paramsObject) throws IllegalAccessException {
        for (Field field : declaredFields) {
            //取出所有被EncryptDecryptField注解的字段
            EncryptDecryptField sensitiveField = field.getAnnotation(EncryptDecryptField.class);
            if (!Objects.isNull(sensitiveField)) {
                field.setAccessible(true);
                Object object = field.get(paramsObject);
                //暂时只实现String类型的加密
                if (object instanceof String) {
                    String value = (String) object;
                    //加密  Des加密工具
                    field.set(paramsObject, DesUtil.encrypt(value,key));
                }
            }
        }
        return paramsObject;
    }
    /**
     * 解密
     *
     * @param result resultType的实例
     * @return T
     * @throws IllegalAccessException 字段不可访问异常
     */
    public static <T> T decrypt(T result) throws IllegalAccessException {
        //取出resultType的类
        Class<?> resultClass = result.getClass();
        Field[] declaredFields = resultClass.getDeclaredFields();
        for (Field field : declaredFields) {
            //取出所有被EncryptDecryptField注解的字段
            EncryptDecryptField sensitiveField = field.getAnnotation(EncryptDecryptField.class);
            if (!Objects.isNull(sensitiveField)) {
                field.setAccessible(true);
                Object object = field.get(result);
                //只支持String的解密
                if (object instanceof String) {
                    String value = (String) object;
                    //对注解的字段进行逐一解密
                    field.set(result, DesUtil.decrypt(value,key));
                }
            }
        }
        return result;
    }
}
EncryptDecrypt

  c.拦截器

写入数据进行加密(insert)

@Intercepts({
        @Signature(type = ParameterHandler.class, method = "setParameters", args = PreparedStatement.class),
})
public class WriteInterceptor implements Interceptor {


    @Override
    public Object intercept(Invocation invocation) throws Throwable {
       //@Signature 指定了 type= parameterHandler 后,这里的 invocation.getTarget() 便是parameterHandler
        //若指定ResultSetHandler ,这里则能强转为ResultSetHandler
        ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();
        // 获取参数对像,即 mapper 中 paramsType 的实例
        Field parameterField = parameterHandler.getClass().getDeclaredField("parameterObject");
        parameterField.setAccessible(true);

        //取出实例
        Object parameterObject = parameterField.get(parameterHandler);
        if (parameterObject != null) {
            Class<?> parameterObjectClass = parameterObject.getClass();
            //校验该实例的类是否被@EncryptDecryptData所注解
            EncryptDecryptData encryptDecryptData = AnnotationUtils.findAnnotation(parameterObjectClass, EncryptDecryptData.class);
            if (Objects.nonNull(encryptDecryptData)) {
                //取出当前当前类所有字段,传入加密方法
                Field[] declaredFields = parameterObjectClass.getDeclaredFields();
                EncryptDecrypt.encrypt(declaredFields, parameterObject);
            }

        }
        return invocation.proceed();
    }
    @Override
    public Object plugin(Object o) {
        //这里必须写入,会判定是否把当前拦截器启动
        return Plugin.wrap(o, this);
    }

}
View Code

 查询数据进行解密

@Intercepts({
        @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})
})
public class ReadInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        //取出查询的结果
        Object resultObject = invocation.proceed();
        if (Objects.isNull(resultObject)) {
            return null;
        }
        //基于selectList
        if (resultObject instanceof ArrayList) {
            ArrayList resultList = (ArrayList) resultObject;
            if (!CollectionUtils.isEmpty(resultList) && needToDecrypt(resultList.get(0))) {
                for (Object result : resultList) {
                    //逐一解密
                    EncryptDecrypt.decrypt(result);
                }
            }
            //基于selectOne
        } else {
            if (needToDecrypt(resultObject)) {
                EncryptDecrypt.decrypt(resultObject);
            }
        }
        return resultObject;
    }

    private boolean needToDecrypt(Object object) {
        Class<?> objectClass = object.getClass();
        EncryptDecryptData sensitiveData = AnnotationUtils.findAnnotation(objectClass, EncryptDecryptData.class);
        return Objects.nonNull(sensitiveData);
    }


    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }
}
View Code
@Intercepts mybatis的注解,用于标记这是一个拦截器,@Signature则表明要拦截的接口、方法以及对应的参数类型,主要类型有如下:
type method 备注
Executor update, query, flushStatements, commit, rollback, getTransaction, close, isClosed 拦截执行器的方法
ParameterHandler getParameterObject, setParameters 拦截参数的处理
ResultSetHandler handleResultSets, handleOutputParameters 拦截结果集的处理
StatementHandler prepare, parameterize, batch, update, query 拦截Sql语法构建的处理

 d.使用注解

@Data
@TableName("user")
@EncryptDecryptData
public class UserDBEntity implements Serializable {

    @TableId(value = "user_id",type = IdType.AUTO)
    private Integer userId;


    @EncryptDecryptField
    @TableField("address")
    private String address;

    @EncryptDecryptField
    @TableField("mobile")
    private String mobile;

    

}
View Code

 e.注册拦截

  在生成的sqlSessionFactory中加入拦截器

@Bean(name = "userSqlSessionFactory")
    public SqlSessionFactory userSqlSessionFactory(@Qualifier("userDB") DataSource dataSource) throws Exception {
        MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        
        //添加插件
        bean.setPlugins(new Interceptor[]{new WriteInterceptor(),new ReadInterceptor()});

       //省略代码
        return bean.getObject();
    }
View Code

 f.使用

 使用拦截器时,因为注解是写在UserDBEntity上,所以插入或查询数据时,要传入UserDBEntity对象,例如:

 1 @Repository
 2 public interface UserMapper {
 3 
 4     @Select("select * from user where mobile = #{mobile} ")
 5     UserDBEntity selectByMobile(TestAddressDBEntity mobile);
 6 
 7 
 8     @Insert(" insert into user (user_id,mobile,address) values (#{userId},#{mobile},#{address})")
 9     int insert(UserDBEntity userInfo);
10      
11 
12 }
13 
14 /*UserDBEntity query = new UserDBEntity();
15         UserDBEntity.setMobile(13711111111);
16         UserDBEntity s =  UserMapper.selectByMobile(query);*/
View Code

 

posted @ 2022-01-07 14:10  Johnson_wang  阅读(1559)  评论(1编辑  收藏  举报