SpringBoot+Mybatis通過自定義注解實現字段加密存儲

😊 @ 作者: 一恍過去
💖 @ 主頁: https://blog.csdn.net/zhuocailing3390
🎊 @ 社區: Java技術棧交流
🎉 @ 主題: SpringBoot+Mybatis實現字段加密
?? @ 創作時間: 2025年04月29日

目錄

  • 前言
  • 實現
    • 自定義注解
    • AES對稱加密工具類
    • 創建攔截器
      • 加密攔截器
      • 解密攔截器
  • 驗證
    • 創建實體類
    • 數據寫入與查詢
  • 加密字段參與查詢
  • 不生效情況

前言

通過Mybatis提供的攔截器,在新增、修改時對特定的敏感字段進行加密存儲,查詢時自動進行解密操作,減少業務層面的代碼邏輯;

加密存儲意義:

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

核心邏輯:

  • 自定義注解,對需要進行加密存儲的使用注解進行標注;
  • 構建AES對稱加密工具類;
  • 實現Mybatis攔截器,通過反射獲取當前實體類的字段是否需要進行加解密;

實現

自定義注解

通過自定義@EncryptDBBean@EncryptDBColumn標識某個DO實體類的某些字段需要進行加解密處理;

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

AES對稱加密工具類


import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;public class DBAESUtils {/*** 設置為CBC加密模式,默認情況下ECB比CBC更高效*/private final static String CBC = "/CBC/PKCS5Padding";private final static String ALGORITHM = "AES";/*** 定義密鑰Key,AES加密算法,key的大小必須是16個字節*/private final static String KEY = "1234567812345678";/*** 設置偏移量,IV值任意16個字節*/private final static String IV = "1122334455667788";/*** 對稱加密數據** @return : 密文* @throws Exception*/public static String encryptBySymmetry(String input) {try {// CBC模式String transformation = ALGORITHM + CBC;// 獲取加密對象Cipher cipher = Cipher.getInstance(transformation);// 創建加密規則// 第一個參數key的字節// 第二個參數表示加密算法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());// 輸出加密后的數據return Base64.getEncoder().encodeToString(bytes);} catch (Exception e) {throw new RuntimeException("加密失敗!", e);}}/*** 對稱解密** @param input : 密文* @throws Exception* @return: 原文*/public static String decryptBySymmetry(String input) {try {// CBC模式String transformation = ALGORITHM + CBC;// 1,獲取Cipher對象Cipher cipher = Cipher.getInstance(transformation);// 指定密鑰規則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));//  因為是明文,所以直接返回return new String(bytes);} catch (Exception e) {throw new RuntimeException("解密失敗!", e);}}
}

創建攔截器

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

加密攔截器

在新增或者更新時,通過攔截對被注解標識的字段進行加密存儲處理;


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 {@Overridepublic 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();// 實體類是否存在 加密注解boolean encryptDBBean = o1Class.isAnnotationPresent(EncryptDBBean.class);if (encryptDBBean) {//取出當前當前類所有字段,傳入加密方法Field[] declaredFields = o1Class.getDeclaredFields();// 便利字段,是否存在加密注解,并且進行加密處理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);}}/*** 默認配置,否則當前攔截器不會加入攔截器鏈*/@Overridepublic Object plugin(Object o) {return Plugin.wrap(o, this);}}

解密攔截器

將查詢的數據,返回為DO實體類時,對被注解標識的字段進行解密處理


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 {@Overridepublic Object intercept(Invocation invocation) throws Throwable {Object resultObject = invocation.proceed();try {if (Objects.isNull(resultObject)) {return null;}// 查詢列表數據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 {// 查詢單個數據Class<?> objectClass = resultObject.getClass();boolean encryptDBBean = objectClass.isAnnotationPresent(EncryptDBBean.class);if (encryptDBBean) {// 解密處理decrypt(resultObject);}}return resultObject;} catch (Exception e) {throw new RuntimeException("字段解密失敗!", e);}}@Overridepublic 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();//對注解的字段進行逐一解密field.set(result, DBAESUtils.decryptBySymmetry(value));}}}}
}

驗證

創建實體類

創建實體類,并且使用加密注解@EncryptDBBean@EncryptDBColumn進行標注,此處以手機號為例;


@Data
@TableName("sys_user_info")
@EncryptDBBean
public class TestEntity {/*** 用戶id*/@TableId("id")private Long id;/*** 用戶名稱*/private String name;/*** 手機號*/@EncryptDBColumnprivate String mobile;
}

數據寫入與查詢

對數據的操作使用偽代碼進行表示

TestEntity entity = new TestEntity();
entity.setId(1L);
entity.setName("測試");
entity.setMobile("166xxxx8888");
// 插入數據
entityService.insert(entity);
// 更新數據
entity.setMobile("166xxxx7777");
entityService.updateById(entity);// 列表查詢
List<TestEntity> list = testService.list();

效果:

  • insert和update后的數據,在數據庫是加密字符串存儲的形式;
  • list方法查詢的數據,將明文進行顯示;

加密字段參與查詢

如果是加密字段進行條件查詢時,需要自行將查詢參數進行加密處理,因為數據庫是存儲的密文,所以查詢時也需要使用密文進行匹配,比如:要查詢mobile=111的數據

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

不生效情況

1、在通過LambdaQueryWrapper獲取QueryWrapper方式查詢時,攔截器無法獲取自定義注解對象,需要手動對查詢的字段進行加密,比如:

如果是 通過自定義的xml查詢,如果入參有加密注解,那么會自動對字段進行加密處理 testMapper.listTest(testEntity)

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

2、使用Mybatis提供的selectOne或者getOne方法查詢時,無法對響應的數據進行解密,需要手動進行處理,比如:

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

TestEntity one = testService.getOne(new QueryWrapper<>(), false);
// mobile在數據庫中加密儲存,此處需要手動進行解密
one.setMobile(DBAESUtils.decryptBySymmetry(one.getMobile()));

在這里插入圖片描述

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/79474.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/79474.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/79474.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Windows 10系統中找回MySQL 8的root密碼

以下是 在Windows 10系統中找回MySQL 8的root密碼 的詳細步驟&#xff1a; 步驟1&#xff1a;停止MySQL服務 按 Win R 輸入 services.msc&#xff0c;打開「服務」管理器。找到 MySQL80&#xff08;或其他自定義服務名&#xff09;&#xff0c;右鍵選擇 停止。 步驟2&#xf…

【計網】互聯網的組成

回顧&#xff1a; 互聯網(Internet)&#xff1a;它是一個專有名詞&#xff0c;是一個特定的互連網&#xff0c;它是指當下全球最大的、最開放的、由眾多網絡相互連接而形成的特定的的互連網&#xff0c;采用TCP/IP協議族作為通信規則。 一、互聯網的組成部分 從互聯網的工作方…

【vue3】黑馬程序員前端Vue3小兔鮮電商項目【八】

黑馬程序員前端Vue3小兔鮮電商項目【八】登錄頁面 登錄頁面的主要功能就是表單校驗和登錄登出業務。 賬號密碼 accountpasswordcdshi0080123456cdshi0081123456cdshi0082123456cdshi0083123456cdshi0084123456cdshi0085123456cdshi0086123456cdshi0087123456cdshi0088123456 …

C++學習:六個月從基礎到就業——C++11/14:右值引用與移動語義

C學習&#xff1a;六個月從基礎到就業——C11/14&#xff1a;右值引用與移動語義 本文是我C學習之旅系列的第三十九篇技術文章&#xff0c;也是第三階段"現代C特性"的第一篇&#xff0c;主要介紹C11/14中引入的右值引用和移動語義。查看完整系列目錄了解更多內容。 引…

基于Qlearning強化學習的電梯群控系統高效調度策略matlab仿真

目錄 1.算法仿真效果 2.算法涉及理論知識概要 2.1 Q-learning強化學習原理 2.2 基于Q-learning的電梯群控系統建模 3.MATLAB核心程序 4.完整算法代碼文件獲得 1.算法仿真效果 matlab2022a仿真結果如下&#xff08;完整代碼運行后無水印&#xff09;&#xff1a; 仿真操作…

31.軟件時序控制方式抗干擾

軟件時序控制方式扛干擾 1. 軟件時序控制抗干擾的時間邏輯2. 應用案例 1. 軟件時序控制抗干擾的時間邏輯 &#xff08;1&#xff09;將受軟件控制的功能或軟件檢測到的狀態一一羅列&#xff1b; &#xff08;2&#xff09;將其中的潛在干擾和敏感信號分開&#xff1b; &#x…

Ubuntu環境下使用uWSGI服務器【以flask應用部署為例】

0、前置內容說明 首先要知道WSGI是什么&#xff0c;關于WSGI服務器的介紹看這篇&#xff1a;WSGI&#xff08;Web Server Gateway Interface&#xff09;服務器 由于從Python 3.11開始限制了在系統級 Python 環境中使用 pip 安裝第三方包&#xff0c;以避免與系統包管理器&am…

d3_v7繪制折線圖

<!DOCTYPE html> <html><head><meta charsetutf-8><title>需求</title><script src"https://d3js.org/d3.v7.min.js"></script><style>* {margin: 0;padding: 0;}html, body {width: 100%;height: 100%;displ…

Hotspot分析(1):單細胞轉錄組識別信息基因(和基因模塊)

這一期我們介紹一個常見的&#xff0c;高分文章引用很高的一個單細胞轉錄組分析工具Hotspot&#xff0c;它可針對單細胞轉錄組數據識別有意義基因或者基因module&#xff0c;類似于聚類模塊。所謂的”informative "的基因是那些在給定度量中相鄰的細胞之間以相似的方式表達…

爬蟲準備前工作

1.Pycham的下載 網址&#xff1a;PyCharm: The only Python IDE you need 2.Python的下載 網址&#xff1a;python.org&#xff08;python3.9版本之后都可以&#xff09; 3.node.js的下載 網址&#xff1a;Node.js — 在任何地方運行 JavaScript&#xff08;版本使用18就可…

基于Springboot旅游網站系統【附源碼】

基于Springboot旅游網站系統 效果如下&#xff1a; 系統登陸頁面 系統主頁面 景點信息推薦頁面 路線詳情頁面 景點詳情頁面 確認下單頁面 景點信息管理頁面 旅游路線管理頁面 研究背景 隨著互聯網技術普及與在線旅游消費習慣的深化&#xff0c;傳統旅游服務模式面臨效率低、…

利用KMP找出模式串在目標串中所有匹配位置的起始下標

問題關鍵&#xff1a;完成首次匹配之后需要繼續進行模式匹配。 到這一步后&#xff0c;我們不能直接將j 0然后開始下一輪匹配&#xff0c;因為已經匹配過的部分&#xff08;藍色部分&#xff09;中仍然可能存在與模式串重疊的子串&#xff1a; 解決辦法&#xff1a; 找到藍…

RR(Repeatable Read)級別如何防止幻讀

在 MySQL 數據庫事務隔離級別中&#xff0c;RR&#xff08;可重復讀&#xff09; 通過 MVCC&#xff08;多版本并發控制&#xff09; 和 鎖機制 的組合策略來避免幻讀問題。 一、MVCC機制&#xff1a;快照讀與版本控制 快照讀&#xff08;Snapshot Read&#xff09; 每個事務啟…

Android運行時ART加載類和方法的過程分析

目錄 一,概述 二,ART運行時的入口 一,概述 既然ART運行時執行的都是翻譯DEX字節碼后得到的本地機器指令了&#xff0c;為什么還需要在OAT文件中包含DEX文件&#xff0c;并且將它加載到內存去呢&#xff1f;這是因為ART運行時提供了Java虛擬機接口&#xff0c;而要實現Java虛…

Javase 基礎加強 —— 02 泛型

本系列為筆者學習Javase的課堂筆記&#xff0c;視頻資源為B站黑馬程序員出品的《黑馬程序員JavaAI智能輔助編程全套視頻教程&#xff0c;java零基礎入門到大牛一套通關》&#xff0c;章節分布參考視頻教程&#xff0c;為同樣學習Javase系列課程的同學們提供參考。 01 認識泛型…

Oracle VirtualBox 在 macOS 上的詳細安裝步驟

Oracle VirtualBox 在 macOS 上的詳細安裝步驟 一、準備工作1. 系統要求2. 下載安裝包二、安裝 VirtualBox1. 掛載安裝鏡像2. 運行安裝程序3. 處理安全限制(僅限首次安裝)三、安裝擴展包(增強功能)四、配置第一個虛擬機1. 創建新虛擬機2. 分配內存3. 創建虛擬硬盤4. 加載系…

RAGFlow 接入企業微信應用實現原理剖析與最佳實踐

背景 近期有醫美行業客戶咨詢我們智能客服產品&#xff0c;期望將自己企業的產品、服務以及報價信息以企微應用的方式給到客戶進行體驗互動&#xff0c;提升企業運營效率。關于企業微信對接&#xff0c;我們分享下最佳實踐&#xff0c;拋磚引玉。效果圖如下&#xff1a; 這里也…

【心海資源】子比主題新增注冊與會員用戶展示功能模塊及實現方法

內容改寫&#xff1a; 本次分享的是子比主題頂部展示注冊用戶與會員信息的功能模塊及其實現方式。 你可以通過兩種方式啟用該功能&#xff1a; 直接在后臺進入“外觀 → 小工具”啟用該展示模塊&#xff0c;操作簡便&#xff1b;也可將提供的代碼覆蓋至子比主題目錄中&#…

CSDN積分詳解(介紹、獲取、用途)

&#x1f91f;致敬讀者 &#x1f7e9;感謝閱讀&#x1f7e6;笑口常開&#x1f7ea;生日快樂?早點睡覺 &#x1f4d8;博主相關 &#x1f7e7;博主信息&#x1f7e8;博客首頁&#x1f7eb;專欄推薦&#x1f7e5;活動信息 文章目錄 積分**一、積分類型及用途****二、積分獲取途…

【iview】es6變量結構賦值(對象賦值)

變量的解構賦值 以iview的src/index.js中Vue.prototype.$IVIEW改造為例練習下怎么使用變量的解構賦值 原來的寫法&#xff1a; const install function(Vue, opts {}) {if (install.installed) return;locale.use(opts.locale);locale.i18n(opts.i18n);Object.keys(iview).fo…