• 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
  • 相关阅读:
    自动化测试-短信验证码处理
    使用HttpClient调用WebAPI接口,含WebAPI端示例
    C#使用HttpClient上传文件并附带其他参数
    WebClient和HttpClient, 以及webapi上传图片
    C#-微信公众平台接口-上传临时素材
    字节组数(二进制流)、Base64、图片(文件)、二进制相互之间转换
    理解并设计rest/restful风格接口
    使用RESTful风格开发
    GitHub OAuth 第三方登录示例教程
    OAuth 2.0 的四种方式
  • 原文地址:https://www.cnblogs.com/wangzun/p/15774811.html
Copyright © 2020-2023  润新知