MyBatis是一款优秀的持久层框架,在应用中使用它可以免除了几乎所有的JDBC代码以及参数设置和结果解析的工作。在企业应用开发中,MyBatis成了很多应用持久层的首选框架。目前在很多企业级应用都有数据安全的要求,本文主要讲述如何基于MyBatis插件实现数据的加解密。
MyBatis框架在实现上使用了动态代理的方式,开发人员只需要定义接口以及接口对应的SQL语句。在以往使用JDBC进行数据库操作时,我们需要加载驱动、创建数据库连接、准备SQL语句、设置参数、执行SQL语句、解析返回结果。如下图1:
MyBatis框架在实现上提供了四大组件来辅助完成JDBC的操作,它们分别是StatementHandler、ParameterHandler、Executor、ResultSetHandler。MyBatis插件允许开发人员在四大组件的执行过程中任意方法进行拦截调用,开发人员只需要实现Interceptor接口,并指定想要拦截的方法签名即可。在上面提到的四大组件中,Executor是执行SQL的组件,在此组件我们可以获得执行前的参数,也可以得到SQL执行后的结果。想要实现数据的加解密,可以在执行前通过反射机制对需要加密的字段进行加密,然后重新设置字段值。解密操作与加密操作类似,对映射结果进行解析,通过反射,判断结果是否有字段需要进行解密。具体流程如下图2:
具体实现代码如下:
- 自定义加解密注解
@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);
}
}