雲栖号資訊:【 點選檢視更多行業資訊】
在這裡您可以找到不同行業的第一手的上雲資訊,還在等什麼,快來!

出于安全考慮,現需要将資料庫的中敏感資訊加密存儲到資料庫中,但是正常業務互動還是需要使用明文資料,是以查詢傳回我們還需要經過相應的解密才能傳回給調用方。
ps:日常開發中,我們要有一定的安全意識,對于密碼,金融資料等敏感資訊事實加密存儲保護。
這個需求說起來不是很難,我們隻需要在執行 sql 之前,提前将指定資料進行加密。執行 sql 之後,擷取傳回結果,再進行的相應的解密。稍微改造下原有代碼,很快完成需求。
現有加密算法如 RSA2 ,AES 等,密文長度将會是明文好幾倍。上線加解密方案一定要評估資料庫現有字段長度是否滿足加密之後長度。
如果這是一張建立的表,上面的實作方案并沒有什麼問題。但是這次我們改造是幾張已有已有千萬級的存量的資料的表,這些資料都未被加密存儲。
如果使用上述代碼,使用加密之後的密文資訊查詢曆史資料,當然查詢不到任何結果。另外當查詢傳回的結果是明文,解密明文資料庫也可能會導緻相應的解密錯誤。
是以為了相容曆史資料,需要進行如下改造:
- 增加新字段存放對應的加密資料,sql 等值條件查詢修改成 in 查詢
- 查詢傳回的記錄首先判斷是否是密文,如果是密文再去解密
代碼改造如下:
上述代碼雖然解決業務需求,但是這個解決方案不是很優雅,業務代碼改動較大,加解密的代碼不能通用,所有涉及到相關字段的方法都需要改動,且幾乎都是重複代碼,代碼侵入性很強,不是很友好。
有經驗的同學可能會想到使用 Spring AOP 解決上述問題。
在切面的前置方法(beforeMethod)統一攔截查詢參數,配合自定義的注解,加密指定的字段。
然後在切面的後置方法(afterReturn)攔截傳回值,配合自定義注解,解密指定的字段。
Spring AOP 代碼實作比較複雜,這裡就不貼出具體的代碼。
但是 Spring AOP 方案也并不通用,如果其他的應用也有相同的需求,同樣的代碼,又需要重複實作,還是很費時費力。
最終我們參考一個 github 開源項目『typehandlers-encrypt』,借助 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 啟動時将會加載該配置檔案。
使用 Spring 配置 Mybatis Bean
配合 Spring 使用時需要将 typeHandler 注入 SqlSessionFactoryBean ,配置方式如下:
SpringBoot
SpringBoot 方式就最簡單了,隻要引入 mybatis-starter,配置檔案加入如下配置即可:
修改 mapper sql 配置
最後我們隻要簡單修改 mapper 中 resultMap 或 sql s配置就可以實作加解密。
假設我們對現有一張 bank_card 表進行加解密,表結構如下:
insert 加密
現需要對 card_no,phone,name,id_no 進行加密,insert 語句加密示例:
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})
我們隻需要在 #{} 指定 typeHandler,傳入參數最後将被加密。使用 typeHandler需要使用類的全路徑,比較繁瑣,我們可以使用 javaType 屬性,直接使用上面我們的定義别名 crypt。
資料庫最終執行sql 如下:
INSERT INTO bank_card (card_no, phone,name,id_no) VALUES ('NjQzMjEyMzEyMzE=', 'MTM1Njc4OTEyMzQ=', '5rWL6K+V5Y2h', 'MTIzMTIzMTIzMQ==');
查詢加解密
普通查詢解密示例如下:
<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"/>
select * from bank_card where id=#{id}
這裡我們在 select 配置中隻能使用 resultMap 屬性,指定 typeHandler 。
資料庫明文、密文共存的情況,查詢解密示例如下:
最後我們可以将自定義的 typeHandler 單獨打包釋出,其他業務方隻需要引用,改造相關配置檔案,即可完成資料加解密。
總結
借助于自定義的 typeHandler,我們實作了一個通用的加解密的方案,該方案對于使用方來說代碼侵入性小,開箱即用,可以快速完成加解密的改造。
【雲栖号線上課堂】每天都有産品技術專家分享!
課程位址:
https://yqh.aliyun.com/zhibo立即加入社群,與專家面對面,及時了解課程最新動态!
【雲栖号線上課堂 社群】
https://c.tb.cn/F3.Z8gvnK
原文釋出時間:2020-04-09
本文作者:樓下小黑哥
本文來自:“掘金”,了解相關資訊可以關注“掘金”