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);*/