Mybatis數據加密解密

文章目錄

      • Mybatis數據加密解密
          • 一、自定義注解
          • 二、自定義參數處理攔截器
          • 結果集攔截器
          • 加密解密

Mybatis數據加密解密

方案一:Mybatis攔截器之數據加密解密【Interceptor】
攔截器介紹
Mybatis Interceptor 在 Mybatis 中被當作 Plugin(插件),不知道為什么,但確實是在 org.apache.ibatis.plugin 包下面

既然是攔截器,可以攔截哪些內容呢?試想一下… 當程序寫到持久層時,Mybatis 會 執行 指定 SQL 語句,并處理 請求參數 和 返回值。沒錯,Mybatis 攔截器可以幫助我們處理上述內容,請看官網的 Plugins 的片段, 內容不多

// 執行
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
// 請求參數處理
ParameterHandler (getParameterObject, setParameters)
// 返回結果集處理
ResultSetHandler (handleResultSets, handleOutputParameters)
// SQL語句構建
StatementHandler (prepare, parameterize, batch, update, query)

攔截器的使用
如果需要實現自定義的攔截器,只需要實現 org.apache.ibatis.plugin.Interceptor 接口,該接口有三個方法:

Object intercept(Invocation invocation) throws Throwable;Object plugin(Object target);void setProperties(Properties properties);

我們要實現數據加密,進入數據庫的字段不能是真實的數據,但是返回來的數據要真實可用,所以我們需要針對 Parameter 和 ResultSet 兩種類型處理,同時為了更靈活的使用,我們需要自定義注解

一、自定義注解

類注解,將注解放在實體類上

/*** 需要加解密的類注解*/
@Documented
@Inherited
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptDecryptClass {
}

字段注解,將注解放在實體字段上

/*** 加密字段注解*/
@Documented
@Inherited
@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptDecryptField {}

有了這兩個注解,我們可以在我們可以標記我們要處理的實體和實體中的字段

二、自定義參數處理攔截器

參考官網,通過 @Intercepts 和 @Signature 的聯合使用,指定 ParameterHandler.class 類型,同時通過 @Component 注解注入到容器中,即可在設置參數的時候進行攔截,通過自定義接口 IEncryptDecrypt, 根據 Field 的各種類型自定義加密解密算法

@Intercepts({@Signature(type = ParameterHandler.class, method = "setParameters", args = PreparedStatement.class),
})
@ConditionalOnProperty(value = "domain.encrypt", havingValue = "true")
@Component
@Slf4j
public class ParammeterInterceptor  implements Interceptor {@Autowiredprivate IEncryptDecrypt encryptDecrypt;@Overridepublic Object intercept(Invocation invocation) throws Throwable {log.info("攔截器ParamInterceptor");//攔截 ParameterHandler 的 setParameters 方法 動態設置參數if (invocation.getTarget() instanceof ParameterHandler) {ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();PreparedStatement ps = (PreparedStatement) invocation.getArgs()[0];// 反射獲取 BoundSql 對象,此對象包含生成的sql和sql的參數map映射/*Field boundSqlField = parameterHandler.getClass().getDeclaredField("boundSql");boundSqlField.setAccessible(true);BoundSql boundSql = (BoundSql) boundSqlField.get(parameterHandler);*/// 反射獲取 參數對像Field parameterField =parameterHandler.getClass().getDeclaredField("parameterObject");parameterField.setAccessible(true);Object parameterObject = parameterField.get(parameterHandler);if (Objects.nonNull(parameterObject)){Class<?> parameterObjectClass = parameterObject.getClass();EncryptDecryptClass encryptDecryptClass = AnnotationUtils.findAnnotation(parameterObjectClass, EncryptDecryptClass.class);if (Objects.nonNull(encryptDecryptClass)){Field[] declaredFields = parameterObjectClass.getDeclaredFields();final Object encrypt = encryptDecrypt.encrypt(declaredFields, parameterObject);}}}return invocation.proceed();}@Overridepublic Object plugin(Object o) {return Plugin.wrap(o, this);}@Overridepublic void setProperties(Properties properties) {}
}

同樣新建結果集攔截器

結果集攔截器

與參數攔截器基本一樣, 只不過類型指定為 ResultSetHandler.class

@Intercepts({@Signature(type = ResultSetHandler.class, method = "handleResultSets", args={Statement.class})
})
@ConditionalOnProperty(value = "domain.decrypt", havingValue = "true")
@Component
@Slf4j
public class ResultInterceptor implements Interceptor {@Autowiredprivate IEncryptDecrypt encryptDecrypt;@Overridepublic Object intercept(Invocation invocation) throws Throwable {Object result = invocation.proceed();if (Objects.isNull(result)){return null;}if (result instanceof ArrayList) {ArrayList resultList = (ArrayList) result;if (CollectionUtils.isNotEmpty(resultList) && needToDecrypt(resultList.get(0))){for (int i = 0; i < resultList.size(); i++) {encryptDecrypt.decrypt(resultList.get(i));}}}else {if (needToDecrypt(result)){encryptDecrypt.decrypt(result);}}return result;}public boolean needToDecrypt(Object object){Class<?> objectClass = object.getClass();EncryptDecryptClass encryptDecryptClass = AnnotationUtils.findAnnotation(objectClass, EncryptDecryptClass.class);if (Objects.nonNull(encryptDecryptClass)){return true;}return false;}@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}@Overridepublic void setProperties(Properties properties) {}
}
加密解密

IEncryptDecrypt 接口定義了 加密和解密兩個方法,

public interface IEncryptDecrypt {/*** 加密方法* @param declaredFields 反射bean成員變量* @param parameterObject Mybatis入參* @param <T>* @return*/public <T> T encrypt(Field[] declaredFields, T parameterObject) throws IllegalAccessException;/*** 解密方法* @param result Mybatis 返回值,需要判斷是否是ArrayList類型* @param <T>* @return*/public <T> T decrypt(T result) throws IllegalAccessException;
}

兩個攔截器通過在 YAML 中配置屬性,按條件注入,外加自定義加密解密算法,完成全局靈活的配置。
核心代碼已上傳至 Github Demo

方案二:Mybatis利用內置類型轉換器【typeHandler】

mybatis 利用內置類型轉換器(「typeHandler」),實現 Java 類型與 JDBC 類型的相互轉換,我們正好可以利用這個特性,在轉換之前加入加解密步驟。

typeHandler 底層原理不是復雜,如果我們沒有使用 Mybatis,而是直接使用最原始的 JDBC 執行查詢語句,相關代碼如下:

我們需要手動判斷 Java 類型,然后調用 PreparedStatement設置合適類型參數。獲取返回結果之后,又需要手動調用 ResultSet 結果集獲取相應類型的數據,這個過程十分繁瑣。使用 mybatis 之后,上述步驟就無需我們再實現了。mybatis 可以通過識別 Java/JDBC 類型,調用相應typeHandler,自動實現轉換邏輯。下圖為 mybatis 內置類型轉換器,基本涵蓋了所有 「Java/JDBC」 數據類型。
在這里插入圖片描述

通用解決方案

自定義 typeHandler

下面我們來實現帶有加解密功能的類型轉換器,實現方式也比較簡單,只要繼承 org.apache.ibatis.type.BaseTypeHandler,重寫相關方法。

簡單起見,上述加解密僅使用了 Base64,大家可以替換成相應加解密算法即或者引入相應加解密服務。
在這里插入圖片描述

其中加密轉換將在 setNonNullParameter 中執行,解密轉換將在 getNullableResult中執行。CryptTypeHandler 使用一個 MappedTypes 注解,包含一個 CryptType 類,這個類使用 mybatis 別名功能,可以極大簡化 sqlmap 相關配置。
在這里插入圖片描述

注冊 typeHandler

使用方必須將 typeHandler 和 alias 注冊到 mybatis 中,否則無法生效。下面提供三種方式,可以根據項目情況選擇其中一種即可:
「單獨使用 mybatis」
這種場景需要在 「mybatis-config.xml」 配置,mybatis 啟動時將會加載該配置文件。

<typeHandlers><!--類型轉換器包路徑--><package name="com.xx.xx"/>
</typeHandlers><!-- 別名定義 -->
<typeAliases><!-- 針對單個別名定義 type:類型的路徑 alias:別名 --><typeAlias type="xx.xx.xx" alias="xx"/>
</typeAliases>

「使用 Spring 配置 Mybatis Bean」

配合 Spring 使用時需要將 typeHandler 注入 SqlSessionFactoryBean ,配置方式如下:

<!-- MyBatis 工廠 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"><property name="dataSource" ref="dataSource" /><!--alias 注入--><property name="typeAliasesPackage" value="xx.xx.xx"/><!--  typeHandlers 注入   --><property name="typeHandlersPackage" value="xx.xx.xx"/>
</bean>

「SpringBoot」

SpringBoot 方式就最簡單了,只要引入 mybatis-starter,配置文件加入如下配置即可:

## mybatis 配置
# 類型轉換器包路徑
mybatis.type-handlers-package=com.xx.xx.x
mybatis.type-aliases-package=com.xx.xx

修改 mapper sql 配置

最后我們只要簡單修改 mapper 中 resultMap 或 sql s配置就可以實現加解密。假設我們對現有一張 「bank_card」 表進行加解密,表結構如下:

CREATE TABLE bank_card (
id int primary key auto_increment,
gmt_create timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
gmt_update timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
card_no varchar(256) NOT NULL DEFAULT '' COMMENT '卡號',
phone varchar(256) NOT NULL DEFAULT '' COMMENT '手機號',
name varchar(256) NOT NULL DEFAULT '' COMMENT '姓名',
id_no varchar(256) NOT NULL DEFAULT '' COMMENT '證件號'
);

「insert 加密」
現需要對 card_no,phone,name,id_no 進行加密,「insert」 語句加密示例:

<insert id="insertBankCard" keyProperty="id" useGeneratedKeys="true" parameterType="org.demo.pojo.BankCardDO">INSERT INTO bank_card (card_no, phone,name,id_no)VALUES(#{card_no,javaType=crypt},#{phone,typeHandler=org.demo.type.CryptTypeHandler},#{name,javaType=crypt},#{id_no,javaType=crypt})
</insert>

我們只需要在 「#{}」 指定 typeHandler,傳入參數最后將被加密。使用 typeHandler需要使用類的全路徑,比較繁瑣,我們可以使用 「javaType」 屬性,直接使用上面我們的定義別名 「crypt」。數據庫最終執行sql 如下:

INSERT INTO bank_card (card_no, phone,name,id_no) VALUES ('NjQzMjEyMzEyMzE=', 'MTM1Njc4OTEyMzQ=', '5rWL6K+V5Y2h', 'MTIzMTIzMTIzMQ==');

推薦一款 IDEA 的插件 「mybatis-log-plugin」,可以自動將 mybatis sql 日志還原成真實執行 sql

「查詢加解密」普通查詢解密示例如下:

<resultMap id="bankCardXml" type="org.demo.pojo.BankCardDO"><result property="card_no" column="card_no" typeHandler="org.demo.type.CryptTypeHandler"/><result property="name" column="name" typeHandler="org.demo.type.CryptTypeHandler"/><result property="id_no" column="id_no" typeHandler="org.demo.type.CryptTypeHandler"/><result property="phone" column="phone" typeHandler="org.demo.type.CryptTypeHandler"/>
</resultMap>
<select id="queryById" resultMap="bankCardXml">select * from bank_card where id=#{id}
</select>

這里我們在 「select」 配置中只能使用 resultMap 屬性,指定 typeHandler 。數據庫明文、密文共存的情況,查詢解密示例如下:

<!-- resultMap 同上   -->
<select id="queryByPhone" resultMap="bankCardXml">select * from bank_card where phone in(#{card_no,javaType=crypt},#{card_no})
</select>

最后我們可以將自定義的 typeHandler 單獨打包發布,其他業務方只需要引用,改造相關配置文件,即可完成數據加解密。上述代碼示例已上傳至 Github

總結
借助于自定義的 typeHandler,我們實現了一個通用的加解密的方案,該方案對于使用方來說代碼侵入性小,開箱即用,可以快速完成加解密的改造。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/20156.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/20156.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/20156.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

ARM32開發——LED點燈

&#x1f3ac; 秋野醬&#xff1a;《個人主頁》 &#x1f525; 個人專欄:《Java專欄》《Python專欄》 ??心若有所向往,何懼道阻且長 文章目錄 點燈的兩種方式灌入電流法輸出電流法擴展板點燈點燈方式點亮LED1-4完整實現 點燈的兩種方式 不同顏色LED&#xff0c;達到相同亮度…

[數據集][目標檢測]貓狗檢測數據集VOC+YOLO格式8291張2類別

數據集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路徑的txt文件&#xff0c;僅僅包含jpg圖片以及對應的VOC格式xml文件和yolo格式txt文件) 圖片數量(jpg文件個數)&#xff1a;8291 標注數量(xml文件個數)&#xff1a;8291 標注數量(txt文件個數)&#xff1a;8291 標注…

ETLCloud中如何使用Kettle組件

ETLCloud中如何使用Kettle組件在當今數據驅動的時代&#xff0c;數據處理和分析已成為企業決策的關鍵。為了更高效地處理海量數據&#xff0c;ETL&#xff08;Extract, Transform, Load&#xff09;工具變得至關重要。而在眾多ETL工具中&#xff0c;Kettle作為一款開源、靈活且…

c#面向對象:接口詳解

接口&#xff08;interface&#xff09; 抽象類中的抽象方法只規定了不能是 private 的&#xff0c;而接口中的“抽象方法”只能是 public 的。這樣的成員訪問級別就決定了接口的本質&#xff1a;接口是服務消費者和服務提供者之間的契約。既然是契約&#xff0c;那就必須是透…

攻防實戰 | 郵件高級威脅檢測與自動化響應

歷經三個月的時間&#xff0c;年度重磅直播節目Fortinet 2024年度“Demo季”近日終于迎來了備受矚目的壓軸大戲——Demo Day第三期&#xff0c;主題為《新郵件安全下的高級威脅檢測與自動化響應》。繼成功舉辦了前兩期《企業網絡中的多源威脅情報自動化整合與集成》和《應急響應…

【持久層】在Spring Boot中使用Hibernate和Gradle構建項目

Hibernate是一個廣泛使用的Java持久化框架&#xff0c;它使得Java對象與關系數據庫之間的映射變得簡單高效。在Spring Boot應用中&#xff0c;結合Gradle構建工具&#xff0c;能夠方便地集成和使用Hibernate。本文將簡述如何在Spring Boot中使用Hibernate&#xff0c;并通過Gra…

Pycharm使用時的紅色波浪線報錯——形如‘break‘ outside loop

背景&#xff1a; 我在一個方法中&#xff0c;寫了一個if判斷&#xff0c;寫了一個break&#xff0c;期望終止這個函數&#xff0c;編輯器出現報錯 形如下圖 視頻版問題教程&#xff1a; Pycharm下出現波浪線報錯&#xff0c;形如break outside loop 過程&#xff1a; 很奇…

IDEA一鍵啟動多個微服務

我們在做微服務項目開發的時候&#xff0c;每次剛打開IDEA&#xff0c;就需要把各個服務一個個依次啟動&#xff0c;特別是服務比較多時&#xff0c;逐個點擊不僅麻煩還費時。下面來說一下如何一鍵啟動多個微服務。 操作步驟 點擊Edit Configurations 2.點擊“”&#xff0c;…

【設計模式】JAVA Design Patterns——Facade(外觀模式)

&#x1f50d;目的 為一個子系統中的一系列接口提供一個統一的接口。外觀定義了一個更高級別的接口以便子系統更容易使用。 &#x1f50d;解釋 真實世界例子 一個金礦是怎么工作的&#xff1f;“嗯&#xff0c;礦工下去然后挖金子&#xff01;”你說。這是你所相信的因為你在使…

性價比為王,物流商怎么選擇高效的國際物流管理平臺

在全球化貿易日益繁榮的今天&#xff0c;國際物流行業作為鏈接國內商家和海外市場的重要橋梁&#xff0c;發揮著極其重要的作用。 然而&#xff0c;隨著國際物流市場競爭的加劇&#xff0c;對物流商來說&#xff0c;也面臨著成本管控和效率提升的雙重挑戰。今天我們會重點探討…

解決 DataGrip 2024.1.3 連接 Tdengine 時timestamp字段顯示時區不正確問題

設置中找到該設置&#xff0c;將原來的設置 yyyy-MM-dd HH:mm:ss 修改為: yyyy-MM-dd HH:mm:ss.SSS z 即可。 注意&#xff1a;只能修改第一個,修改后提示錯誤&#xff0c;但是查詢數據時能成功格式化時間&#xff0c;修改第二個不生效&#xff0c;可能是 bug 具體格式見: Date…

DOS編程入門:探索基礎、深入技巧與實戰應用

DOS編程入門&#xff1a;探索基礎、深入技巧與實戰應用 DOS編程&#xff0c;作為計算機編程的基石之一&#xff0c;對于初學者來說&#xff0c;既是一種挑戰&#xff0c;也是一次深入了解計算機底層運作的絕佳機會。本文將從四個方面、五個方面、六個方面和七個方面&#xff0…

Opera 瀏覽器與Google聯手,推出由Gemini驅動的全新AI功能

每周跟蹤AI熱點新聞動向和震撼發展 想要探索生成式人工智能的前沿進展嗎&#xff1f;訂閱我們的簡報&#xff0c;深入解析最新的技術突破、實際應用案例和未來的趨勢。與全球數同行一同&#xff0c;從行業內部的深度分析和實用指南中受益。不要錯過這個機會&#xff0c;成為AI領…

JavaScript循環語句

JavaScript中的循環語句有三種&#xff1a;for循環、while循環和do...while循環。這些循環語句可以幫助我們重復執行一段代碼&#xff0c;直到滿足某個條件為止。 for循環&#xff1a; for循環是最常用的循環語句之一&#xff0c;它包含一個初始化表達式、一個條件表達式和一個…

富士攝像機X-H2S MOV格式化后重新寫入后的恢復方法

X-H2S是富士數碼的一款旗艦機型&#xff0c;支持4K/6K高清&#xff0c;視頻編碼為最新的HVC。下面我們來看下富士數碼攝像機恢復案例。 故障存儲:512G存儲卡 Exfat文件系統 故障現象: 512G的卡誤格式化后又進行了拍攝&#xff0c;卡使用了120G不到的空間&#xff0c;其它底…

【副業】12種程序員副業大匯總

1&#xff1a;寫博客。技術能力可以的寫技術文章輸出&#xff0c;比如當前網站、掘金、阿里云社區、騰訊云社區、其他社區。 2&#xff1a;賣課程。大廠高P跟知識付費平臺合作、錄課賣課程、比如極課時間、慕課網、騰訊課堂。 3&#xff1a;寫書。技術大拿出書&#xff0c;掙稿…

簡述Vue中同時發送多個請求怎么操作?

在Vue中同時發送多個請求&#xff0c;我們通常使用axios這個庫&#xff0c;因為它基于Promise&#xff0c;可以很好地處理異步操作。以下是兩種常用的方法來同時發送多個請求&#xff1a; 方法一&#xff1a;使用Promise.all() 定義多個請求&#xff1a; 使用axios.get()或axi…

【EFK日志系統】docker一鍵部署kibana、es-head

docker一鍵部署kibana、es-head kibana部署es-head部署 上一篇文章搭建了es集群 規劃服務器是 es01:172.23.165.185 es02:172.23.165.186 es03:172.23.165.187 那么kibana就搭建在主節點es01:172.23.165.185 按照順序參考&#xff1a; docker一鍵部署EFK系統&#xff08;elas…

使用 Vue 3 和 JsBarcode 開發一維碼顯示組件

在現代前端開發中&#xff0c;條形碼&#xff08;或稱一維碼&#xff09;在許多應用場景中非常常見&#xff0c;例如商品管理、物流跟蹤等。本文將介紹如何使用 Vue 3 和 JsBarcode 庫來創建一個靈活的一維碼顯示組件&#xff0c;并展示如何在應用中使用它。 1. 安裝必要的依賴…

簡述Vue 的響應式原理中 Object.defineProperty 有什 么缺陷 ?

Vue.js 2.x 的響應式原理主要依賴于 Object.defineProperty 方法來實現數據劫持&#xff0c;即當數據發生變化時&#xff0c;能夠觸發視圖更新。然而&#xff0c;Object.defineProperty 方法在 Vue 的響應式系統中存在一些缺陷&#xff1a; 無法監聽數組的變化&#xff1a; Obj…