Mybatis自动加解密

发布时间:2024年01月12日

涉及隐私信息的字段需要加密存储数据库,返回给前端时又需要解密显示正确信息。故采用mybatis自动加解密的方案,该方案基于自定义注解+拦截器进行实现。加密后的信息不支持模糊匹配(可参考业界流行方案,基于业务需求做分词或采用其他方案以支持模糊匹配,本文不涉及)。

网上的解决方案会有以下问题(已在代码中完善解决,不再细述):

1.对于对象List中的需加密属性没有加密

2.重复加密,导致密文有误

1.引入hutool-all依赖(加解密所使用依赖)

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.8.10</version>
</dependency>

2.定义加解密工具类

//加解密工具类
public class EncryptDecryptUtil {

    private final static Logger logger = LoggerFactory.getLogger(EncryptDecryptUtil.class);

    /**
     * 对称加密的秘钥
     */
    private final static String key = "加密密钥";
    /**
     * * 加密     *
     * * @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加密工具
                    String encryptHexStr = SecureUtil.des(key.getBytes()).encryptHex(value);
                    //logger.info(value+"加密后是"+encryptHexStr);
                    field.set(paramsObject, encryptHexStr);
                }
            }
        }
        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;
                    //对注解的字段进行逐一解密
                    String decryptStr = SecureUtil.des(key.getBytes()).decryptStr(value);
                    //logger.info("{}字段需要解密,{}解密后的值是{}",field.getName(),value,decryptStr);
                    field.set(result, decryptStr);
                }
            }
        }
        return result;
    }

}

3.创建自定义注解类

/**
 * 该注解定义在类上
 * 插件通过扫描类对象是否包含这个注解来决定是否继续扫描其中的字段注解
 * 这个注解要配合EncryptDecryptField注解
 **/
@Inherited
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptDecryptClass {
}
/**
 * 该注解有两种使用方式
 * ①:配合@EncryptDecryptClass加在类中的字段上
 * ②:直接在Mapper中的方法参数上使用
 **/
@Inherited
@Target({ ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptDecryptField {
}

4.配置自定义的mybatis读写拦截器

//写入数据拦截器
@Intercepts({
        @Signature(type = ParameterHandler.class,
                method = "setParameters",
                args = PreparedStatement.class)
})
public class WriteInterceptor implements Interceptor {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();
        Object testParame=parameterHandler.getParameterObject();

        Field parameterField = parameterHandler.getClass().getDeclaredField("parameterObject");
        parameterField.setAccessible(true);        //取出实例
        Object parameterObject = parameterField.get(parameterHandler);

        if (parameterObject != null) {
            Map<String,Object> test= BeanUtil.beanToMap(parameterObject);
            Collection values = test.values();
            Set<Object> setObj = new LinkedHashSet<>(values);
            for (Object value : setObj) {
                if(value instanceof List){
                    for(Object item : (List<?>) value){
                        Class<?> parameterObjectClass = item.getClass();
                        //校验该实例的类是否被EncryptDecryptClass所注解
                        EncryptDecryptClass encryptDecryptClass =
                                AnnotationUtils.findAnnotation(parameterObjectClass, EncryptDecryptClass.class);
                        if (Objects.nonNull(encryptDecryptClass)) {
                            //取出当前当前类所有字段,传入加密方法
                            Field[] declaredFields = parameterObjectClass.getDeclaredFields();
                            EncryptDecryptUtil.encrypt(declaredFields, item);
                        }
                    }
                }else {
                    Class<?> parameterObjectClass = value.getClass();
                    //校验该实例的类是否被EncryptDecryptClass所注解
                    EncryptDecryptClass encryptDecryptClass =
                            AnnotationUtils.findAnnotation(parameterObjectClass, EncryptDecryptClass.class);
                    if (Objects.nonNull(encryptDecryptClass)) {
                        //取出当前当前类所有字段,传入加密方法
                        Field[] declaredFields = parameterObjectClass.getDeclaredFields();
                        EncryptDecryptUtil.encrypt(declaredFields, value);
                    }
                }
            }

        }
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object o) {
        //这里必须写入,会判定是否把当前拦截器启动
        return Plugin.wrap(o, this);
    }
}
//读取数据拦截器
@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) {
                    //逐一解密
                    EncryptDecryptUtil.decrypt(result);
                }
            }
            //基于selectOne
        } else {
            if (needToDecrypt(resultObject)) {
                EncryptDecryptUtil.decrypt(resultObject);
            }
        }
        return resultObject;
    }

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

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }
}
//配置自定义的mybatis拦截器
@Configuration
@Component
public class MybatisCryptoConfig {

    @Bean
    public ReadInterceptor readInterceptorPlugin(){
        return new ReadInterceptor();
    }

    @Bean
    public WriteInterceptor writeInterceptorPlugin(){
        return new WriteInterceptor();
    }
}

5.在实体类上使用注解

在涉及需要加解密的实体类上使用@EncryptDecryptClass注解,在需要加解密的属性上使用@EncryptDecryptField注解。

/**
 * 管理员信息
 */
@Data
@EncryptDecryptClass
public class SysAdminVo {

    /**
     * 管理员id
     * @mock 123455
    */
    private String admin_id;

    /**
     * 管理员姓名
     * @mock 张三
    */
    private String admin_name;

    /**
     * 管理员手机号;
     * @mock 13112341234
    */
    @EncryptDecryptField
    private String admin_phone;

}

6.使用说明

在需要自动加解密的时候,请使用已配置注解的实体类作为入参或出参,否则不会进行加解密转换。

参考链接:

【Mybatis】基于Mybatis插件+注解,实现敏感数据自动加解密

原生mybatis实现数据加密存储和读取

springboot+mybatis+自定义注解实现对数据库中的字段进行加解密

文章来源:https://blog.csdn.net/woai671/article/details/135547793
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。