MyBatis-Plus攔截器對敏感數(shù)據(jù)實現(xiàn)加密
做課程項目petstore時遇到需要加密屬性的問題,而MyBatis-Plus為開發(fā)者提供了攔截器的相關接口,用于與數(shù)據(jù)庫交互的過程中實現(xiàn)特定功能,本文主要介紹通過MyBatis-Plus的攔截器接口自定義一個攔截器類實現(xiàn)敏感數(shù)據(jù)如用戶密碼的加密功能,即實現(xiàn)在DAO層寫入數(shù)據(jù)庫時傳入明文,而數(shù)據(jù)庫中存儲的是密文。由于加密算法有多種,這里不展示具體的加密步驟,主要討論攔截器的構建。
一、定義注解
自定義相關注解,將需要加密的字段及其所在的實體類進行標注,方便攔截器攔截時的判斷。這里定義了兩個注解(分別形成兩個不同的文件),@SensitiveData和@SensitiveField,分別用于注解實體類、實體類中需要加密的屬性。注意,注解@Target內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í)行語句的過程中對特定對象進行攔截調用,主要有以下四種情況:
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed) 處理增刪改查
- ParameterHandler (getParameterObject, setParameters) 設置預編譯參數(shù)
- ResultSetHandler (handleResultSets, handleOutputParameters) 處理結果
- StatementHandler (prepare, parameterize, batch, update, query) 處理sql預編譯,設置參數(shù)
通過@Intercepts注解內部的@Signature注解進行配置,有三個配置選項分別為type、method、args,type用于指定上述四類中的某一類,method用于指定該類型中的哪個方法執(zhí)行時被攔截,args用于接收被攔截方法的參數(shù)。觀察注解@Signature的代碼可以加深理解:
這里選取ParameterHandler類型的setParameters方法,在每次執(zhí)行sql之前設置參數(shù)前進行攔截加密。整個攔截器類重寫intercept方法用于加密、重寫plugin方法用于將該攔截器接入攔截器鏈(這里可以選擇不重寫plugin方法,因為Interceptor類中定義的該方法默認內容與我們重寫的內容是一樣的),代碼如下:
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,這里則能強轉為ResultSetHandler
ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();
// 獲取參數(shù)對像,即對應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;
}
構建過程中主要是使用了Java的反射機制來處理。在構建的過程中需要注意的一點是,我們需要在更新、插入時進行加密,但是網(wǎng)上很多方法都是默認為插入時加密,所以取出來的parameterObject對象都是默認為這個表對應的實體類。但是更新操作不同,更新操作時打印parameterObject對象的類型可看到是org.apache.ibatis.binding.MapperMethod$ParamMap,即取出的parameterObject對象是一個ParamMap類,而其中"et"的key對應的value才是我們需要的實體類,因此這里需要通過判斷parameterObject對象的類型來分類進行處理。
如果選用的是雙向加密算法(可逆),還可以設計一個用于解密的攔截器類進行處理,選取ResultSetHandler類型的handleResultSets方法,在處理結果集之前進行解密,解密的情況根據(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) {}
}
到此這篇關于MyBatis-Plus攔截器對敏感數(shù)據(jù)實現(xiàn)加密的文章就介紹到這了,更多相關MyBatis-Plus攔截器對敏感數(shù)據(jù)加密內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
- MybatisPlus攔截器如何實現(xiàn)數(shù)據(jù)表分表
- mybatis-plus配置攔截器實現(xiàn)sql完整打印的代碼設計
- MyBatis-Plus攔截器實現(xiàn)數(shù)據(jù)權限控制的方法
- Mybatis-plus通過添加攔截器實現(xiàn)簡單數(shù)據(jù)權限
- MybatisPlusInterceptor實現(xiàn)sql攔截器超詳細教程
- MyBatis-Plus攔截器實現(xiàn)數(shù)據(jù)權限控制的示例
- mybatis-plus 攔截器敏感字段加解密的實現(xiàn)
- mybatis-plus攔截器、字段填充器、類型處理器、表名替換、SqlInjector(聯(lián)合主鍵處理)
- mybatisplus 的SQL攔截器實現(xiàn)關聯(lián)查詢功能
- Mybatis Plus 3.4.0分頁攔截器的用法小結
相關文章
深入Parquet文件格式設計原理及實現(xiàn)細節(jié)
這篇文章主要介紹了深入Parquet文件格式設計原理及實現(xiàn)細節(jié),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-08-08
Spring Boot 深入分析AutoConfigurationImportFilter自動化條件
這篇文章主要分析了Spring Boot AutoConfigurationImportFilter自動化條件配置源碼,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習吧2022-07-07
idea?compile項目正常啟動項目的時候build失敗報“找不到符號”等問題及解決方案
這篇文章主要介紹了idea?compile項目正常,啟動項目的時候build失敗,報“找不到符號”等問題,這種問題屬于lombok編譯失敗導致,可能原因是依賴jar包沒有更新到最新版本,需要的朋友可以參考下2023-10-10
springboot+vue3無感知刷新token實戰(zhàn)教程
本文介紹了基于Spring Boot和Vue3的無感知刷新Token的實現(xiàn),包括后端token構造和刷新邏輯,以及前端的請求處理和緩存機制2025-03-03
Java數(shù)據(jù)結構及算法實例:三角數(shù)字
這篇文章主要介紹了Java數(shù)據(jù)結構及算法實例:三角數(shù)字,本文直接給出實現(xiàn)代碼,代碼中包含詳細注釋,需要的朋友可以參考下2015-06-06

