MyBatis-Plus攔截器對敏感數(shù)據(jù)實現(xiàn)加密
做課程項目petstore時遇到需要加密屬性的問題,而MyBatis-Plus為開發(fā)者提供了攔截器的相關(guān)接口,用于與數(shù)據(jù)庫交互的過程中實現(xiàn)特定功能,本文主要介紹通過MyBatis-Plus的攔截器接口自定義一個攔截器類實現(xiàn)敏感數(shù)據(jù)如用戶密碼的加密功能,即實現(xiàn)在DAO層寫入數(shù)據(jù)庫時傳入明文,而數(shù)據(jù)庫中存儲的是密文。由于加密算法有多種,這里不展示具體的加密步驟,主要討論攔截器的構(gòu)建。
一、定義注解
自定義相關(guān)注解,將需要加密的字段及其所在的實體類進行標注,方便攔截器攔截時的判斷。這里定義了兩個注解(分別形成兩個不同的文件),@SensitiveData和@SensitiveField,分別用于注解實體類、實體類中需要加密的屬性。注意,注解@Target內(nèi)ElementType取值為TYPE時表示該注解用于注解類,取值為FIELD表示該注解用于注解類的屬性。
package org.csu.mypetstore.api.utils.encrypt.annotation; import java.lang.annotation.*; @Inherited @Target({ ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) public @interface SensitiveData { }
二、定義攔截器類
自定義的攔截器類需要實現(xiàn)MyBatis的Interceptor類,主要是重寫三個方法public Object intercept(Invocation invocation)
、public Object plugin(Object target)
、public void setProperties(Properties properties)
。這三個方法前兩個我們需要使用到,第三個方法這里暫時用不到。
為我們創(chuàng)建的攔截器類打上@Component注解使之被Spring容器所管理;打上@Intercepts注解用于標識攔截器開始攔截的情況(執(zhí)行sql語句過程中的哪個位置)。Mybatis可以在執(zhí)行語句的過程中對特定對象進行攔截調(diào)用,主要有以下四種情況:
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed) 處理增刪改查
- ParameterHandler (getParameterObject, setParameters) 設(shè)置預(yù)編譯參數(shù)
- ResultSetHandler (handleResultSets, handleOutputParameters) 處理結(jié)果
- StatementHandler (prepare, parameterize, batch, update, query) 處理sql預(yù)編譯,設(shè)置參數(shù)
通過@Intercepts注解內(nèi)部的@Signature注解進行配置,有三個配置選項分別為type、method、args,type用于指定上述四類中的某一類,method用于指定該類型中的哪個方法執(zhí)行時被攔截,args用于接收被攔截方法的參數(shù)。觀察注解@Signature的代碼可以加深理解:
這里選取ParameterHandler類型的setParameters方法,在每次執(zhí)行sql之前設(shè)置參數(shù)前進行攔截加密。整個攔截器類重寫intercept方法用于加密、重寫plugin方法用于將該攔截器接入攔截器鏈(這里可以選擇不重寫plugin方法,因為Interceptor類中定義的該方法默認內(nèi)容與我們重寫的內(nèi)容是一樣的),代碼如下:
package org.csu.mypetstore.api.utils.encrypt; import lombok.extern.slf4j.Slf4j; import org.apache.ibatis.binding.MapperMethod; import org.apache.ibatis.executor.parameter.ParameterHandler; import org.apache.ibatis.plugin.*; import org.csu.mypetstore.api.utils.encrypt.annotation.SensitiveData; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.stereotype.Component; import java.lang.reflect.Field; import java.sql.PreparedStatement; import java.util.Map; import java.util.Objects; import java.util.Properties; /** * 加密攔截器:插入數(shù)據(jù)庫之前對敏感數(shù)據(jù)加密 * 場景:插入、更新時生效 * 策略: * - 在敏感字段所在實體類上添加@SensitiveData注解 * - 在敏感字段上添加@SensitiveField注解 * * @author csu_cangkui * @date 2021/8/14 */ @Slf4j @Component @Intercepts({ @Signature(type = ParameterHandler.class, method = "setParameters", args = PreparedStatement.class) }) public class EncryptInterceptor implements Interceptor { // 更新時的參數(shù)名稱,ParamMap的key值 private static final String CRYPT = "et"; @Override public Object intercept(Invocation invocation) throws Throwable { try { // @Signature 指定了 type= parameterHandler.class 后,這里的 invocation.getTarget() 便是parameterHandler // 若指定ResultSetHandler,這里則能強轉(zhuǎn)為ResultSetHandler ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget(); // 獲取參數(shù)對像,即對應(yīng)MyBatis的 mapper 中 paramsType 的實例 Field parameterField = parameterHandler.getClass().getDeclaredField("parameterObject"); parameterField.setAccessible(true); // 取出參數(shù)實例 Object parameterObject = parameterHandler.getParameterObject(); if (parameterObject != null) { Object sensitiveObject = null; if (parameterObject instanceof MapperMethod.ParamMap) { // 更新操作被攔截 Map paramMap = (Map) parameterObject; sensitiveObject = paramMap.get(CRYPT); } else { // 插入操作被攔截,parameterObject即為待插入的實體對象 sensitiveObject = parameterObject; } // 獲取不到數(shù)據(jù)就直接放行 if (Objects.isNull(sensitiveObject)) return invocation.proceed(); // 校驗該實例的類是否被@SensitiveData所注解 Class<?> sensitiveObjectClass = sensitiveObject.getClass(); SensitiveData sensitiveData = AnnotationUtils.findAnnotation(sensitiveObjectClass, SensitiveData.class); if (Objects.nonNull(sensitiveData)) { // 如果是被注解的類,則進行加密 // 取出當前當前類所有字段,傳入加密方法 Field[] declaredFields = sensitiveObjectClass.getDeclaredFields(); EncryptUtil.encrypt(declaredFields, sensitiveObject, EncryptUtil.ENCRYPT_MD5_MODE); } } return invocation.proceed(); } catch (Exception e) { // 未作更多處理,加密失敗仍然會放行讓數(shù)據(jù)進入數(shù)據(jù)庫 log.error("加密失敗", e); } return invocation.proceed(); } @Override public Object plugin(Object target) { // 將這個攔截器接入攔截器鏈 return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) {} }
EncryptUtil類的encrypt加密方法的大致處理流程:
static <T> T encrypt(Field[] declaredFields, T sensitiveObject, final int ENCRYPT_MODE) throws IllegalAccessException { for (Field field : declaredFields) { // 取出所有被SensitiveField注解的字段 SensitiveField sensitiveField = field.getAnnotation(SensitiveField.class); if (Objects.nonNull(sensitiveField)) { field.setAccessible(true); Object targetProperty = field.get(sensitiveObject); // 僅討論對字符串類型的字段的加密 if (targetProperty instanceof String) { // 取得源字段值 String value = (String) targetProperty; String valueEncrypt; if (ENCRYPT_MODE == ENCRYPT_MD5_MODE) { // 使用MD5加密算法進行加密 valueEncrypt = EncryptUtil.MD5Encrypt(value); } else if (ENCRYPT_MODE == ENCRYPT_AES_MODE) { // 使用AES加密算法進行加密 valueEncrypt = EncryptUtil.AESEncrypt(value); } else { valueEncrypt = value; } // 將加密完成的字段值放入待用參數(shù)對象 field.set(sensitiveObject, valueEncrypt); } } } return sensitiveObject; }
構(gòu)建過程中主要是使用了Java的反射機制來處理。在構(gòu)建的過程中需要注意的一點是,我們需要在更新、插入時進行加密,但是網(wǎng)上很多方法都是默認為插入時加密,所以取出來的parameterObject對象都是默認為這個表對應(yīng)的實體類。但是更新操作不同,更新操作時打印parameterObject對象的類型可看到是org.apache.ibatis.binding.MapperMethod$ParamMap,即取出的parameterObject對象是一個ParamMap類,而其中"et"的key對應(yīng)的value才是我們需要的實體類,因此這里需要通過判斷parameterObject對象的類型來分類進行處理。
如果選用的是雙向加密算法(可逆),還可以設(shè)計一個用于解密的攔截器類進行處理,選取ResultSetHandler類型的handleResultSets方法,在處理結(jié)果集之前進行解密,解密的情況根據(jù)具體需求來確定,可以基于selectList方法、基于selectOne方法等等。
package org.csu.mypetstore.api.utils.encrypt; import lombok.extern.slf4j.Slf4j; import org.apache.ibatis.executor.resultset.ResultSetHandler; import org.apache.ibatis.plugin.*; import org.csu.mypetstore.api.utils.encrypt.annotation.SensitiveData; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.stereotype.Component; import java.beans.Statement; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.Properties; @Slf4j @Component @Intercepts({ @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = Statement.class) }) public class DecryptInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { Object resultObject = invocation.proceed(); try { if (Objects.isNull(resultObject)) return null; if (resultObject instanceof ArrayList) { // 基于selectList List resultList = (ArrayList) resultObject; if (!resultList.isEmpty() && needToDecrypt(resultList.get(0))) { for (Object result : resultList) { //逐一解密 EncryptUtils.decrypt(result); } } } else { // 基于selectOne if (needToDecrypt(resultObject)) EncryptUtils.decrypt((String) resultObject); } return resultObject; } catch (Exception e) { log.error("解密失敗", e); } return resultObject; } // 判斷是否是需要解密的敏感實體類 private boolean needToDecrypt(Object object) { Class<?> objectClass = object.getClass(); SensitiveData sensitiveData = AnnotationUtils.findAnnotation(objectClass, SensitiveData.class); return Objects.nonNull(sensitiveData); } @Override public Object plugin(Object target) { // 將這個攔截器接入攔截器鏈 return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) {} }
到此這篇關(guān)于MyBatis-Plus攔截器對敏感數(shù)據(jù)實現(xiàn)加密的文章就介紹到這了,更多相關(guān)MyBatis-Plus攔截器對敏感數(shù)據(jù)加密內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- MybatisPlus攔截器如何實現(xiàn)數(shù)據(jù)表分表
- mybatis-plus配置攔截器實現(xiàn)sql完整打印的代碼設(shè)計
- MyBatis-Plus攔截器實現(xiàn)數(shù)據(jù)權(quán)限控制的方法
- Mybatis-plus通過添加攔截器實現(xiàn)簡單數(shù)據(jù)權(quán)限
- MybatisPlusInterceptor實現(xiàn)sql攔截器超詳細教程
- MyBatis-Plus攔截器實現(xiàn)數(shù)據(jù)權(quán)限控制的示例
- mybatis-plus 攔截器敏感字段加解密的實現(xiàn)
- mybatis-plus攔截器、字段填充器、類型處理器、表名替換、SqlInjector(聯(lián)合主鍵處理)
- mybatisplus 的SQL攔截器實現(xiàn)關(guān)聯(lián)查詢功能
- Mybatis Plus 3.4.0分頁攔截器的用法小結(jié)
相關(guān)文章
詳解Java動態(tài)加載數(shù)據(jù)庫驅(qū)動
本篇文章主要介紹了詳解Java動態(tài)加載數(shù)據(jù)庫驅(qū)動,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-05-05深入Parquet文件格式設(shè)計原理及實現(xiàn)細節(jié)
這篇文章主要介紹了深入Parquet文件格式設(shè)計原理及實現(xiàn)細節(jié),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-08-08Spring Boot 深入分析AutoConfigurationImportFilter自動化條件
這篇文章主要分析了Spring Boot AutoConfigurationImportFilter自動化條件配置源碼,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習吧2022-07-07idea?compile項目正常啟動項目的時候build失敗報“找不到符號”等問題及解決方案
這篇文章主要介紹了idea?compile項目正常,啟動項目的時候build失敗,報“找不到符號”等問題,這種問題屬于lombok編譯失敗導致,可能原因是依賴jar包沒有更新到最新版本,需要的朋友可以參考下2023-10-10springboot+vue3無感知刷新token實戰(zhàn)教程
本文介紹了基于Spring Boot和Vue3的無感知刷新Token的實現(xiàn),包括后端token構(gòu)造和刷新邏輯,以及前端的請求處理和緩存機制2025-03-03Java數(shù)據(jù)結(jié)構(gòu)及算法實例:三角數(shù)字
這篇文章主要介紹了Java數(shù)據(jù)結(jié)構(gòu)及算法實例:三角數(shù)字,本文直接給出實現(xiàn)代碼,代碼中包含詳細注釋,需要的朋友可以參考下2015-06-06