基于MyBatis插件实现数据加解密

405 阅读3分钟

MyBatis是一款优秀的持久层框架,在应用中使用它可以免除了几乎所有的JDBC代码以及参数设置和结果解析的工作。在企业应用开发中,MyBatis成了很多应用持久层的首选框架。目前在很多企业级应用都有数据安全的要求,本文主要讲述如何基于MyBatis插件实现数据的加解密。

MyBatis框架在实现上使用了动态代理的方式,开发人员只需要定义接口以及接口对应的SQL语句。在以往使用JDBC进行数据库操作时,我们需要加载驱动、创建数据库连接、准备SQL语句、设置参数、执行SQL语句、解析返回结果。如下图1:

图片 1.png

MyBatis框架在实现上提供了四大组件来辅助完成JDBC的操作,它们分别是StatementHandler、ParameterHandler、Executor、ResultSetHandler。MyBatis插件允许开发人员在四大组件的执行过程中任意方法进行拦截调用,开发人员只需要实现Interceptor接口,并指定想要拦截的方法签名即可。在上面提到的四大组件中,Executor是执行SQL的组件,在此组件我们可以获得执行前的参数,也可以得到SQL执行后的结果。想要实现数据的加解密,可以在执行前通过反射机制对需要加密的字段进行加密,然后重新设置字段值。解密操作与加密操作类似,对映射结果进行解析,通过反射,判断结果是否有字段需要进行解密。具体流程如下图2:

图片 2.png

具体实现代码如下:

  1. 自定义加解密注解
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})
public @interface FieldEncrypt {

    String password() default "";

    Algorithm algorithm() default Algorithm.AES;
}

加解密参数有两个,一个对称加密的密码,另一个是使用的加密算法。目前支持对称AES、DES对称加密以及RSA非对称加密。

public enum Algorithm {
    AES,
    DES,
    RSA;
    private Algorithm() {
    }
}

2.自定义拦截器

@Intercepts(
        {
                @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}),
                @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
        }
)
@Component
public class EncryptInterceptor implements Interceptor, ApplicationContextAware {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object target = invocation.getTarget();
        Object[] args = invocation.getArgs();
        Object parameter = args[1];
        boolean isUpdate = args.length == 2;
        if (isUpdate) {
            // 判断是否修改方法
            Field[] fields = parameter.getClass().getDeclaredFields();
            for (Field field : fields) {
                field.setAccessible(true);
                if (field.isAnnotationPresent(FieldEncrypt.class)) {
                    FieldEncrypt fieldEncrypt = field.getDeclaredAnnotation(FieldEncrypt.class);
                    AlgorithmHandler algorithmHandler = applicationContext.getBean(fieldEncrypt.algorithm().name() + "AlgorithmHandler", AlgorithmHandler.class);
                    String oldValue = (String) field.get(parameter);
                    field.set(parameter, algorithmHandler.encryptStr(fieldEncrypt.password(), oldValue));
                }
            }
        } else {
            final Executor executor = (Executor) target;
            MappedStatement ms = (MappedStatement) args[0];
            RowBounds rowBounds = (RowBounds) args[2];
            ResultHandler resultHandler = (ResultHandler) args[3];
            BoundSql boundSql;
            if (args.length == 4) {
                boundSql = ms.getBoundSql(parameter);
            } else {
                boundSql = (BoundSql) args[5];
            }
            CacheKey cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
            List list = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
            if (list != null) {
                for (Object object : list) {
                    Field[] fields = object.getClass().getDeclaredFields();
                    for (Field field : fields) {
                        field.setAccessible(true);
                        if (field.isAnnotationPresent(FieldEncrypt.class)) {
                            String oldValue = (String) field.get(object);
                            FieldEncrypt fieldEncrypt = field.getDeclaredAnnotation(FieldEncrypt.class);
                            AlgorithmHandler algorithmHandler = applicationContext.getBean(fieldEncrypt.algorithm().name() + "AlgorithmHandler", AlgorithmHandler.class);
                            field.set(object, algorithmHandler.decryptStr(fieldEncrypt.password(), oldValue));
                        }
                    }
                }
            }
            return list;
        }
        return invocation.proceed();
    }

    ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}
public interface AlgorithmHandler {

    /**
     * 加密字符串
     * @param password
     * @param oldValue
     * @return
     */
    String encryptStr(String password, String oldValue);

    /**
     * 解密字符串
     * @param password
     * @param oldValue
     * @return
     */
    String decryptStr(String password, String oldValue);
}
@Component
public class AESAlgorithmHandler implements AlgorithmHandler {

    @Value("${mybatis.encrypt.password}")
    private String password;

    @Override
    public String encryptStr(String password, String oldValue) {
        AES aes;
        if (StringUtils.isNotBlank(password)) {
            aes = SecureUtil.aes(password.getBytes());
        } else {
            aes = SecureUtil.aes(this.password.getBytes());
        }
        return aes.encryptHex(oldValue);
    }

    @Override
    public String decryptStr(String password, String oldValue) {
        AES aes;
        if (StringUtils.isNotBlank(password)) {
            aes = SecureUtil.aes(password.getBytes());
        } else {
            aes = SecureUtil.aes(this.password.getBytes());
        }
        return aes.decryptStr(oldValue);
    }
}
@Component
public class DESAlgorithmHandler implements AlgorithmHandler {

    @Value("${mybatis.encrypt.password}")
    private String password;

    @Override
    public String encryptStr(String password, String oldValue) {
        DES des;
        if (StringUtils.isNotBlank(password)) {
            des = SecureUtil.des(password.getBytes());
        } else {
            des = SecureUtil.des(this.password.getBytes());
        }
        return des.encryptHex(oldValue);
    }

    @Override
    public String decryptStr(String password, String oldValue) {
        DES des;
        if (StringUtils.isNotBlank(password)) {
            des = SecureUtil.des(password.getBytes());
        } else {
            des = SecureUtil.des(this.password.getBytes());
        }
        return des.decryptStr(oldValue);
    }
}
@Component
public class RSAAlgorithmHandler implements AlgorithmHandler {

    @Value("${mybatis.encrypt.privateKey}")
    private String privateKey;

    @Value("${mybatis.encrypt.publicKey}")
    private String publicKey;

    private RSA rsa;

    @Override
    public String encryptStr(String password, String oldValue) {
        return rsa.encryptHex(oldValue, KeyType.PublicKey);
    }

    @Override
    public String decryptStr(String password, String oldValue) {
        return rsa.decryptStr(oldValue, KeyType.PrivateKey);
    }

    @PostConstruct
    public void initRSA() {
        this.rsa = new RSA(privateKey, publicKey);
    }
}