資料加密概述
加密:將明文資訊改變為難以讀取的密文內容,
解密:將密文內容轉化為原來資料,
分類
- 對稱加密:加密與解密密鑰相同,
- 非對稱加密:加密使用公鑰,公鑰可公開;解密使用私鑰,
相關閱讀:
- 加密 - wikipedia
資料庫隱私欄位加密注解組件實作
說明
- 資料庫存盤密文欄位,記憶體可見為明文資訊
- 可設定欄位保存值支持 整體加密(僅可全部匹配查詢)、模糊加密(支持模糊查詢)
- 相關做法: 資料庫隱私欄位加密以及加密后的資料如何進行模糊查詢? - 業余草
MyBatis-Plus 實作(做法:使用 常規2 處理)
-
整體加密資料庫欄位長度與保存值長度對應(需要額外存盤一個識別符號)
資料庫欄位長度 字符長度 varchar(49) 12個中文字符、36個ascii字符 varchar(97) 24個中文字符、72個ascii字符、3個ascii字符+23個中文字符、68個ascii字符+1個中文字符 varchar(197) 49個中文字符、145個ascii字符、3個ascii字符+48個中文字符 、 142個ascii字符+1個中文字符 varchar(253) 63個中文字符、189個ascii字符 、3個ascii字符+62個中文字符 、 186個ascii字符+1個中文字符 -
模糊加密資料庫欄位產犢與保存值長度對應(4個位元組加密一次大致對應8個字符+1個識別符號),根據4位英文字符(半角),2個中文字符(全角)為一個檢索條件
資料庫欄位長度 字符長度 varchar(99) 12個中文字符、14個ascii字符 varchar(198) 23個中文字符、25個ascii字符 varchar(252) 29個中文字符、31個ascii字符
1 :利用 TypeHandler 支持資料加解密轉換
INSERT VALUE、UPDATE ENTITY、SELECT RESULT
只對資料庫和程式之間的資料轉換,查詢條件不會呼叫,
-
整體加密型別處理器
使用示例: 1. MyBatis-Plus 注解(自動生產 ResultMap ,存在場景不生效) @TableField(typeHandler = OverallCryptoTypeHandler.class) 2. 自定義 ResultMap 配置 <result column="phone" property="phone" typeHandler="cn.eastx.practice.demo.crypto.config.mp.OverallCryptoTypeHandler" />整體加密型別處理器 OverallCryptoTypeHandler.java
@Override public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException { /* 對非null引數值進行加密,需要通過物體類處理方可,支持 INSERT/UPDATE ENTITY 當前處理 INSERT ENTITY,UPDATE ENTITY 會先通過攔截器處理 因為攔截器修改元資料將導致物體類屬性值產生變更,所以物體類還是由 TypeHandler 來進行處理 */ ps.setString(i, CryptoDataUtil.overallEncrypt(parameter)); } @Override public String getNullableResult(ResultSet rs, String columnName) throws SQLException { // 對可為null的結果進行解密 return CryptoDataUtil.decrypt(rs.getString(columnName)); } -
模糊加密型別處理器
注意:根據4位英文字符(半角),2個中文字符(全角)為一個檢索條件,如果欄位值較少查詢可能存在問題使用示例: 1. MyBatis-Plus 注解(自動生產 ResultMap ,存在場景不生效) @TableField(typeHandler = FuzzyCryptoTypeHandler.class) 2. 自定義 ResultMap 配置 <result column="phone" property="phone" typeHandler="cn.eastx.practice.demo.crypto.config.mp.FuzzyCryptoTypeHandler" />模糊加密型別處理器 FuzzyCryptoTypeHandler.java
@Override public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException { /* 對非null引數值進行加密,需要通過物體類處理方可,支持 INSERT/UPDATE ENTITY 當前處理 INSERT ENTITY,UPDATE ENTITY 會先通過攔截器處理 因為攔截器修改元資料將導致物體類屬性值產生變更,所以物體類還是由 TypeHandler 來進行處理 */ ps.setString(i, CryptoDataUtil.fuzzyEncrypt(parameter)); } @Override public String getNullableResult(ResultSet rs, String columnName) throws SQLException { // 對可為null的結果進行解密 return CryptoDataUtil.decrypt(rs.getString(columnName)); }
2 :利用欄位注解配合 MyBatis 攔截器對條件進行攔截處理
注:目前僅支持簡單查詢處理,復雜查詢可能存在問題,
-
自定義欄位注解
CryptoCond.java- 使用
replacedColumn()替換 SQL 查詢條件中的欄位名 - 使用
encryption()對 SQL 中條件值、引數值進行加密,支持兩種方式(整體匹配、模糊匹配) - 使用示例:
User.java
- 使用
-
自定義 MyBatis 攔截器
- 實作 Interceptor 介面,重寫攔截器攔截 SQL 邏輯
- 攔截器執行在 TypeHandler 之前,注意避免沖突
自定義 MyBatis 攔截器 CryptoCondInterceptor.java
@Override public Object intercept(Invocation invocation) throws Throwable { StatementHandler statementHandler = PluginUtils.realTarget(invocation.getTarget()); MetaObject metaObject = SystemMetaObject.forObject(statementHandler); MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement"); // 支持處理 SELECT、UPDATE、DELETE boolean canHandler = Stream.of(SqlCommandType.SELECT, SqlCommandType.UPDATE, SqlCommandType.DELETE) .anyMatch(item -> item.equals(mappedStatement.getSqlCommandType())); if (canHandler && !getIntercept()) { clearIntercept(); return invocation.proceed(); } clearIntercept(); // 判斷是否有引數需要處理 BoundSql boundSql = statementHandler.getBoundSql(); if (Objects.isNull(boundSql.getParameterObject())) { return invocation.proceed(); } // 獲取自定義注解,通過 MapperID 獲取到 Mapper 對應的物體類,獲取物體類所有注解欄位與注解對應 Map Map<String, CryptoCond> condMap = mapEntityFieldCond(mappedStatement.getId()); if (CollectionUtil.isNotEmpty(condMap)) { replaceHandle(mappedStatement.getConfiguration(), condMap, boundSql); } return invocation.proceed(); } // 替換資料處理 private void replaceHandle(Configuration configuration, Map<String, CryptoCond> condMap, BoundSql boundSql) { String sql = boundSql.getSql(); List<SqlCondOperation> operationList = SqlUtil.listSqlCondOperation(sql); if (CollectionUtil.isEmpty(operationList)) { return; } MetaObject paramMetaObject = configuration.newMetaObject(boundSql.getParameterObject()); List<ParameterMapping> mappings = boundSql.getParameterMappings(); int condParamStart = SqlUtil.getSqlCondParamStartIdx(sql); int mappingStartIdx = 0; for (SqlCondOperation operation : operationList) { String columnName = operation.getColumnName(); String condStr = operation.getOriginCond(); int condNum = SqlUtil.countPreparePlaceholder(condStr); CryptoCond ann = condMap.get(operation.getColumnName()); if (Objects.nonNull(ann)) { // 替換查詢條件引數中的列名 if (StrUtil.isNotBlank(ann.replacedColumn()) && condParamStart < operation.getOriginCondStartIdx()) { sql = sql.replace(condStr, condStr.replace(columnName, ann.replacedColumn())); } // 替換屬性值為加密值 if (condNum == 0) { // 存在非預編譯陳述句條件,直接替換 SQL 條件值 String propVal = String.valueOf(paramMetaObject.getValue(columnName)); String useVal = getCryptoUseVal(ann, propVal); sql = sql.replace(condStr, condStr.replace(propVal, useVal)); } else { // 預編譯陳述句條件通過替換條件值處理 for (int i = 0; i < condNum; i++) { String propName = mappings.get(mappingStartIdx + i).getProperty(); if (!propName.startsWith("et.")) { // 非物體類屬性進行值替換,物體類屬性通過 TypeHandler 處理 String propVal = String.valueOf(paramMetaObject.getValue(propName)); paramMetaObject.setValue(propName, getCryptoUseVal(ann, propVal)); } } } } mappingStartIdx += condNum; } ReflectUtil.setFieldValue(boundSql, "sql", sql); }
3 :相關匯總
測驗
- 執行 /resources/db/schema.sql 創建資料庫( java-practice-demos )、示例表( crypto-user )
- 用戶表 Service 層
IUserService.java - 用戶表 Service 層 測驗
UserServiceTest.java

使用問題
-
TypeHandler 不起效
- 在 xml 中自定義 ResultMap ,示例:
UserMapper.xml
點擊查看代碼 UserMapper.xml
<!-- 使用自定義SQL時,對于加密處理需要使用ResultMap作為回傳物件,否則對決議成實際資料會存在問題 --> <resultMap id="BaseResultMap" type="cn.eastx.practice.demo.crypto.pojo.po.User"> <result column="id" property="id" /> <result column="name" property="name" /> <result column="password" property="password" /> <result column="salt" property="salt" /> <result column="phone" property="phone" typeHandler="cn.eastx.practice.demo.crypto.config.mp.OverallCryptoTypeHandler" /> <result column="email" property="email" typeHandler="cn.eastx.practice.demo.crypto.config.mp.FuzzyCryptoTypeHandler" /> <result column="create_time" property="createTime" /> <result column="update_time" property="updateTime" /> </resultMap>- 在物體類上增加
@TableName(autoResultMap = true),自動構建 ResultMap - https://gitee.com/baomidou/mybatis-plus/issues/I103ZO
- 在 xml 中自定義 ResultMap ,示例:
-
加密后解密資料亂碼
- 可能是密鑰存在問題,建議重新生成
-
CryptoCondInterceptor 不起效
- 設定 CryptoCondInterceptor.setIntercept(false)
- 在物體類上相應欄位設定
@CryptoCond,示例:User.java
用戶物體類 User.java
@Data @TableName(value = "https://www.cnblogs.com/cnx01/archive/2022/11/13/crypto_user", autoResultMap = true) public class User { /** * 用戶表主鍵ID */ private Long id; /** * 用戶名 */ private String name; /** * 加密后的密碼,MD5加鹽 */ private String password; /** * 加密密碼使用的鹽 */ private String salt; /** * 手機號碼,整體加密 */ @TableField(typeHandler = OverallCryptoTypeHandler.class) @CryptoCond(encryption = CryptoCond.EncryptionEnum.DEFAULT_OVERALL) private String phone; /** * 郵箱,模糊加密 */ @TableField(typeHandler = FuzzyCryptoTypeHandler.class) @CryptoCond(encryption = CryptoCond.EncryptionEnum.DEFAULT_FUZZY) private String email; /** * 創建時間 */ @TableField(fill = INSERT) private LocalDateTime createTime; /** * 更新時間 */ @TableField(fill = INSERT_UPDATE) private LocalDateTime updateTime; }- Mapper 實作 BaseMapper 并指定物體類,示例:
public interface UserMapper extends BaseMapper<User> - 將攔截器加入 Spring IOC 管理
-
MySQL 例外
- 索引長度
MySQL 默認索引長度最大長度是767bytes
Specified key was too long; max key length is 3072
bytes
- 索引長度
參考
- mybatis(mybatis-plus)使用sql攔截器和自定義注解獲取sql和引數
其他
demo 地址:https://github.com/EastX/java-practice-demos/tree/main/demo-crypto
推薦閱讀:
- 資料庫隱私欄位加密以及加密后的資料如何進行模糊查詢? - 業余草
本文來自博客園,轉載請注明原文鏈接:https://www.cnblogs.com/cnx01/p/16887088.html
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/532540.html
標籤:其他
上一篇:day17-Servlet06
下一篇:<六>指向類成員的指標
