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

