最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SpringBoot+Mybatis通過自定義注解實(shí)現(xiàn)字段加密存儲(chǔ)方式

 更新時(shí)間:2026年03月02日 09:44:00   作者:一恍過去  
本文介紹了如何使用Mybatis攔截器對(duì)敏感字段進(jìn)行加密存儲(chǔ)和查詢時(shí)的自動(dòng)解密操作,通過自定義注解和AES對(duì)稱加密工具類實(shí)現(xiàn),減少業(yè)務(wù)層面的代碼邏輯,提高數(shù)據(jù)安全性

前言

通過Mybatis提供的攔截器,在新增、修改時(shí)對(duì)特定的敏感字段進(jìn)行加密存儲(chǔ),查詢時(shí)自動(dòng)進(jìn)行解密操作,減少業(yè)務(wù)層面的代碼邏輯;

加密存儲(chǔ)意義:

  • 防止數(shù)據(jù)泄露:即使數(shù)據(jù)庫被非法訪問或泄露,加密數(shù)據(jù)也無法被直接利用
  • 保護(hù)個(gè)人隱私:如身份證號(hào)、手機(jī)號(hào)、住址等PII(個(gè)人身份信息)數(shù)據(jù)
  • 保障財(cái)務(wù)安全:加密銀行卡號(hào)、支付密碼等金融信息

核心邏輯:

  • 自定義注解,對(duì)需要進(jìn)行加密存儲(chǔ)的使用注解進(jìn)行標(biāo)注;
  • 構(gòu)建AES對(duì)稱加密工具類;
  • 實(shí)現(xiàn)Mybatis攔截器,通過反射獲取當(dāng)前實(shí)體類的字段是否需要進(jìn)行加解密;

實(shí)現(xiàn)

自定義注解

通過自定義@EncryptDBBean@EncryptDBColumn標(biāo)識(shí)某個(gè)DO實(shí)體類的某些字段需要進(jìn)行加解密處理;

  • EncryptDBBean:作用在類上
  • EncryptDBColumn:作用在字段上
@Inherited
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptDBBean {
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface EncryptDBColumn {
}

AES對(duì)稱加密工具類

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;

public class DBAESUtils {
    /**
     * 設(shè)置為CBC加密模式,默認(rèn)情況下ECB比CBC更高效
     */
    private final static String CBC = "/CBC/PKCS5Padding";
    private final static String ALGORITHM = "AES";

    /**
     * 定義密鑰Key,AES加密算法,key的大小必須是16個(gè)字節(jié)
     */
    private final static String KEY = "1234567812345678";

    /**
     * 設(shè)置偏移量,IV值任意16個(gè)字節(jié)
     */
    private final static String IV = "1122334455667788";

    /**
     * 對(duì)稱加密數(shù)據(jù)
     *
     * @return : 密文
     * @throws Exception
     */
    public static String encryptBySymmetry(String input) {
        try {
            // CBC模式
            String transformation = ALGORITHM + CBC;
            // 獲取加密對(duì)象
            Cipher cipher = Cipher.getInstance(transformation);
            // 創(chuàng)建加密規(guī)則
            // 第一個(gè)參數(shù)key的字節(jié)
            // 第二個(gè)參數(shù)表示加密算法
            SecretKeySpec sks = new SecretKeySpec(KEY.getBytes(), ALGORITHM);

            // ENCRYPT_MODE:加密模式
            // DECRYPT_MODE: 解密模式
            // 使用CBC模式
            IvParameterSpec iv = new IvParameterSpec(IV.getBytes());
            cipher.init(Cipher.ENCRYPT_MODE, sks, iv);


            // 加密
            byte[] bytes = cipher.doFinal(input.getBytes());

            // 輸出加密后的數(shù)據(jù)
            return Base64.getEncoder().encodeToString(bytes);
        } catch (Exception e) {
            throw new RuntimeException("加密失??!", e);
        }
    }

    /**
     * 對(duì)稱解密
     *
     * @param input : 密文
     * @throws Exception
     * @return: 原文
     */
    public static String decryptBySymmetry(String input) {
        try {
            // CBC模式
            String transformation = ALGORITHM + CBC;

            // 1,獲取Cipher對(duì)象
            Cipher cipher = Cipher.getInstance(transformation);
            // 指定密鑰規(guī)則
            SecretKeySpec sks = new SecretKeySpec(KEY.getBytes(), ALGORITHM);

            // 使用CBC模式
            IvParameterSpec iv = new IvParameterSpec(IV.getBytes());
            cipher.init(Cipher.DECRYPT_MODE, sks, iv);

            // 3. 解密,上面使用的base64編碼,下面直接用密文
            byte[] bytes = cipher.doFinal(Base64.getDecoder().decode(input));
            //  因?yàn)槭敲魑?,所以直接返?
            return new String(bytes);
        } catch (Exception e) {
            throw new RuntimeException("解密失??!", e);
        }
    }
}

創(chuàng)建攔截器

  • 加密攔截器:EncryptInterceptor
  • 解密攔截器:DecryptInterceptor

加密攔截器

在新增或者更新時(shí),通過攔截對(duì)被注解標(biāo)識(shí)的字段進(jìn)行加密存儲(chǔ)處理;

import com.lhz.demo.annotation.EncryptDBBean;
import com.lhz.demo.annotation.EncryptDBColumn;
import com.lhz.demo.utils.DBAESUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.plugin.*;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.sql.PreparedStatement;
import java.util.*;

@Slf4j
@Component
@Intercepts({
        @Signature(type = ParameterHandler.class, method = "setParameters", args = {PreparedStatement.class}),
})
public class EncryptInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        try {
            ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();
            Field parameterField = parameterHandler.getClass().getDeclaredField("parameterObject");
            parameterField.setAccessible(true);
            Object parameterObject = parameterField.get(parameterHandler);

            if (parameterObject != null) {
                Set<Object> objectList = new HashSet<>();
                if (parameterObject instanceof Map<?, ?>) {
                    Collection<?> values = ((Map<?, ?>) parameterObject).values();
                    objectList.addAll(values);
                } else {
                    objectList.add(parameterObject);
                }
                for (Object o1 : objectList) {
                    Class<?> o1Class = o1.getClass();
                    // 實(shí)體類是否存在 加密注解
                    boolean encryptDBBean = o1Class.isAnnotationPresent(EncryptDBBean.class);
                    if (encryptDBBean) {
                        //取出當(dāng)前當(dāng)前類所有字段,傳入加密方法
                        Field[] declaredFields = o1Class.getDeclaredFields();
                        // 便利字段,是否存在加密注解,并且進(jìn)行加密處理
                        for (Field field : declaredFields) {
                            //取出所有被EncryptDecryptField注解的字段
                            boolean annotationPresent = field.isAnnotationPresent(EncryptDBColumn.class);
                            if (annotationPresent) {
                                field.setAccessible(true);
                                Object object = field.get(o1);
                                if (object != null) {
                                    String value = object.toString();
                                    //加密  這里我使用自定義的AES加密工具
                                    field.set(o1, DBAESUtils.encryptBySymmetry(value));
                                }
                            }
                        }
                    }
                }
            }
            return invocation.proceed();
        } catch (Exception e) {
            throw new RuntimeException("字段加密失敗!", e);
        }
    }

    /**
     * 默認(rèn)配置,否則當(dāng)前攔截器不會(huì)加入攔截器鏈
     */
    @Override
    public Object plugin(Object o) {
        return Plugin.wrap(o, this);
    }

}

解密攔截器

將查詢的數(shù)據(jù),返回為DO實(shí)體類時(shí),對(duì)被注解標(biāo)識(shí)的字段進(jìn)行解密處理

import com.lhz.demo.annotation.EncryptDBBean;
import com.lhz.demo.annotation.EncryptDBColumn;
import com.lhz.demo.utils.DBAESUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.plugin.*;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.lang.reflect.Field;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

@Intercepts({@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})})
@Slf4j
@Component
public class DecryptInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object resultObject = invocation.proceed();
        try {
            if (Objects.isNull(resultObject)) {
                return null;
            }
            // 查詢列表數(shù)據(jù)
            if (resultObject instanceof ArrayList) {
                List list = (ArrayList) resultObject;
                if (!CollectionUtils.isEmpty(list)) {
                    for (Object result : list) {
                        Class<?> objectClass = result.getClass();
                        boolean encryptDBBean = objectClass.isAnnotationPresent(EncryptDBBean.class);
                        if (encryptDBBean) {
                            // 解密處理
                            decrypt(result);
                        }
                    }
                }
            } else {
                // 查詢單個(gè)數(shù)據(jù)
                Class<?> objectClass = resultObject.getClass();
                boolean encryptDBBean = objectClass.isAnnotationPresent(EncryptDBBean.class);
                if (encryptDBBean) {
                    // 解密處理
                    decrypt(resultObject);
                }
            }
            return resultObject;
        } catch (Exception e) {
            throw new RuntimeException("字段解密失??!", e);
        }

    }

    @Override
    public Object plugin(Object o) {
        return Plugin.wrap(o, this);
    }

    public <T> void decrypt(T result) throws Exception {
        //取出resultType的類
        Class<?> resultClass = result.getClass();
        Field[] declaredFields = resultClass.getDeclaredFields();
        for (Field field : declaredFields) {
            boolean annotationPresent = field.isAnnotationPresent(EncryptDBColumn.class);
            if (annotationPresent) {
                field.setAccessible(true);
                Object object = field.get(result);
                if (object != null) {
                    String value = object.toString();
                    //對(duì)注解的字段進(jìn)行逐一解密
                    field.set(result, DBAESUtils.decryptBySymmetry(value));
                }
            }
        }
    }
}

驗(yàn)證

創(chuàng)建實(shí)體類

創(chuàng)建實(shí)體類,并且使用加密注解@EncryptDBBean、@EncryptDBColumn進(jìn)行標(biāo)注,此處以手機(jī)號(hào)為例;

@Data
@TableName("sys_user_info")
@EncryptDBBean
public class TestEntity {
    /**
     * 用戶id
     */
    @TableId("id")
    private Long id;

    /**
     * 用戶名稱
     */
    private String name;

    /**
     * 手機(jī)號(hào)
     */
    @EncryptDBColumn
    private String mobile;
}

數(shù)據(jù)寫入與查詢

對(duì)數(shù)據(jù)的操作使用偽代碼進(jìn)行表示

TestEntity entity = new TestEntity();
entity.setId(1L);
entity.setName("測試");
entity.setMobile("166xxxx8888");
// 插入數(shù)據(jù)
entityService.insert(entity);
// 更新數(shù)據(jù)
entity.setMobile("166xxxx7777");
entityService.updateById(entity);


// 列表查詢
List<TestEntity> list = testService.list();

效果:

  • insert和update后的數(shù)據(jù),在數(shù)據(jù)庫是加密字符串存儲(chǔ)的形式;
  • list方法查詢的數(shù)據(jù),將明文進(jìn)行顯示;

加密字段參與查詢

如果是加密字段進(jìn)行條件查詢時(shí),需要自行將查詢參數(shù)進(jìn)行加密處理,因?yàn)閿?shù)據(jù)庫是存儲(chǔ)的密文,所以查詢時(shí)也需要使用密文進(jìn)行匹配,比如:要查詢mobile=111的數(shù)據(jù)

// 偽代碼
// 獲取前端傳入的查詢條件
String mobile = "111"
// 手動(dòng)加密
mobile = DBAESUtils.decryptBySymmetry(mobile );
testService.selectByMobile(mobile);

不生效情況

1、在通過LambdaQueryWrapper獲取QueryWrapper方式查詢時(shí),攔截器無法獲取自定義注解對(duì)象,需要手動(dòng)對(duì)查詢的字段進(jìn)行加密,比如:

如果是 通過自定義的xml查詢,如果入?yún)⒂屑用茏⒔?,那么?huì)自動(dòng)對(duì)字段進(jìn)行加密處理 testMapper.listTest(testEntity)

LambdaQueryWrapper<TestEntity> wrapper = new LambdaQueryWrapper<>();
String mobile = test.getMobile();
if (mobile != null) {
   // mobile在數(shù)據(jù)庫中加密儲(chǔ)存,此處需要手動(dòng)進(jìn)行加密
   mobile = DBAESUtils.encryptBySymmetry(mobile);
}
wrapper.eq(StringUtils.isNotBlank(test.getMobile()), TestEntity::getMobile, mobile);
List<TestEntity> testEntities = testMapper.selectList(wrapper);

2、使用Mybatis提供的selectOne或者getOne方法查詢時(shí),無法對(duì)響應(yīng)的數(shù)據(jù)進(jìn)行解密,需要手動(dòng)進(jìn)行處理,比如:

如果是 通過自定義的xml查詢,無論多少條數(shù)據(jù)都會(huì)對(duì)數(shù)據(jù)進(jìn)行解密,testMapper.selectXmlById(Long id)

TestEntity one = testService.getOne(new QueryWrapper<>(), false);
// mobile在數(shù)據(jù)庫中加密儲(chǔ)存,此處需要手動(dòng)進(jìn)行解密
one.setMobile(DBAESUtils.decryptBySymmetry(one.getMobile()));

總結(jié)

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • 2022年最新java?8?(?jdk1.8u321)安裝圖文教程

    2022年最新java?8?(?jdk1.8u321)安裝圖文教程

    這篇文章主要介紹了2022年最新java?8?(?jdk1.8u321)安裝圖文教程,截止2022年1月,官方出的jdk1.8目前已更新到8u321的版本,本文通過圖文并茂的形式給大家介紹安裝過程,需要的朋友可以參考下
    2022-08-08
  • Java9新特性中的模塊化詳解

    Java9新特性中的模塊化詳解

    今天介紹一個(gè)Java?9的功能,模塊化(Modular),這可能使Java有史以來最大的Feature,對(duì)Java9模塊化相關(guān)知識(shí)感興趣的朋友一起看看吧
    2022-03-03
  • Java Annotation注解相關(guān)原理代碼總結(jié)

    Java Annotation注解相關(guān)原理代碼總結(jié)

    這篇文章主要介紹了Java Annotation注解相關(guān)原理代碼總結(jié),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-07-07
  • java內(nèi)部類原理與用法詳解

    java內(nèi)部類原理與用法詳解

    這篇文章主要介紹了java內(nèi)部類原理與用法,結(jié)合實(shí)例形式分析了Java內(nèi)部類的概念、原理、分類及相關(guān)使用技巧,需要的朋友可以參考下
    2019-05-05
  • MyBatis樹形結(jié)構(gòu)查詢的實(shí)現(xiàn)示例

    MyBatis樹形結(jié)構(gòu)查詢的實(shí)現(xiàn)示例

    本文介紹了一種使用MyBatis進(jìn)行樹形結(jié)構(gòu)查詢的方法,通過構(gòu)造臨時(shí)表和內(nèi)連接,實(shí)現(xiàn)了不同層級(jí)數(shù)據(jù)的一次性查詢及映射,感興趣的可以了解一下
    2025-11-11
  • Java?+?Selenium?+?OpenCV解決自動(dòng)化測試中的滑塊驗(yàn)證問題

    Java?+?Selenium?+?OpenCV解決自動(dòng)化測試中的滑塊驗(yàn)證問題

    OpenCV是一個(gè)基于Apache2.0許可(開源)發(fā)行的跨平臺(tái)計(jì)算機(jī)視覺和機(jī)器學(xué)習(xí)軟件庫,可以運(yùn)行在Linux、Windows、Android和Mac?OS操作系統(tǒng)上,這篇文章主要介紹了Java?+?Selenium?+?OpenCV解決自動(dòng)化測試中的滑塊驗(yàn)證,需要的朋友可以參考下
    2022-07-07
  • 解決redisTemplate中l(wèi)eftPushAll隱性bug的問題

    解決redisTemplate中l(wèi)eftPushAll隱性bug的問題

    這篇文章主要介紹了解決redisTemplate中l(wèi)eftPushAll隱性bug的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2021-02-02
  • JavaScript Date時(shí)間對(duì)象的常用操作方法總結(jié)

    JavaScript Date時(shí)間對(duì)象的常用操作方法總結(jié)

    本文介紹了JavaScript中Date對(duì)象的常用操作方法,包括創(chuàng)建、獲取、設(shè)置本地及UTC時(shí)間等,幫助開發(fā)者靈活處理日期與時(shí)間的相關(guān)需求,需要的朋友可以參考下
    2025-10-10
  • 使用@Cacheable注解Redis時(shí)Redis宕機(jī)或其他原因連不上繼續(xù)調(diào)用原方法的解決方案

    使用@Cacheable注解Redis時(shí)Redis宕機(jī)或其他原因連不上繼續(xù)調(diào)用原方法的解決方案

    在Spring Boot應(yīng)用中,我們經(jīng)常使用??@Cacheable??注解來緩存數(shù)據(jù),以提高應(yīng)用的性能,本文將探討如何在Redis不可用時(shí),讓??@Cacheable??注解繼續(xù)調(diào)用原方法,確保服務(wù)的可用性和穩(wěn)定性,感興趣的朋友一起看看吧
    2025-05-05
  • SpringBoot如何使用applicationContext.xml配置文件

    SpringBoot如何使用applicationContext.xml配置文件

    這篇文章主要介紹了SpringBoot使用applicationContext.xml配置文件,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-06-06

最新評(píng)論

大同县| 拜城县| 安图县| 舒兰市| 敖汉旗| 临西县| 吉水县| 深水埗区| 阳春市| 三河市| 遵义市| 吴桥县| 龙川县| 涞源县| 舒兰市| 靖州| 大邑县| 宜川县| 富裕县| 崇左市| 湟源县| 读书| 昌平区| 宁远县| 丹寨县| 长白| 孝义市| 吉水县| 小金县| 汝南县| 扎囊县| 华池县| 潞城市| 河东区| 南充市| 通江县| 枣阳市| 宜兰县| 鹤壁市| 沅陵县| 蓝田县|