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 { }
EncryptDecryptField 该注解用于标记拦截器用于加密的字段
@Inherited @Target({ ElementType.FIELD }) @Retention(RetentionPolicy.RUNTIME) public @interface 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; } }
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); } }
查询数据进行解密
@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); } }
@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; }
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(); }
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);*/