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 {
}
EncryptDecryptData

  EncryptDecryptField 該注解用於標記攔截器用於加密的字段

@Inherited
@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptDecryptField {
}
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;
    }
}
EncryptDecrypt

  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);
    }

}
View Code

 查詢數據進行解密

@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);
    }
}
View Code
@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;

    

}
View Code

 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();
    }
View Code

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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM