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

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

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

前言

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

加密存儲意義:

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

核心邏輯:

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

實現(xiàn)

自定義注解

通過自定義@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個字節(jié)
     */
    private final static String KEY = "1234567812345678";

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

    /**
     * 對稱加密數(shù)據(jù)
     *
     * @return : 密文
     * @throws Exception
     */
    public static String encryptBySymmetry(String input) {
        try {
            // CBC模式
            String transformation = ALGORITHM + CBC;
            // 獲取加密對象
            Cipher cipher = Cipher.getInstance(transformation);
            // 創(chuàng)建加密規(guī)則
            // 第一個參數(shù)key的字節(jié)
            // 第二個參數(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);
        }
    }

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

            // 1,獲取Cipher對象
            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));
            //  因為是明文,所以直接返回
            return new String(bytes);
        } catch (Exception e) {
            throw new RuntimeException("解密失??!", e);
        }
    }
}

創(chuàng)建攔截器

  • 加密攔截器: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 {

    @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();
                    // 實體類是否存在 加密注解
                    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);
        }
    }

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

}

解密攔截器

將查詢的數(shù)據(jù),返回為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 {
    @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 {
                // 查詢單個數(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();
                    //對注解的字段進行逐一解密
                    field.set(result, DBAESUtils.decryptBySymmetry(value));
                }
            }
        }
    }
}

驗證

創(chuàng)建實體類

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

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

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

    /**
     * 手機號
     */
    @EncryptDBColumn
    private String mobile;
}

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

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

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ù)庫是加密字符串存儲的形式;
  • list方法查詢的數(shù)據(jù),將明文進行顯示;

加密字段參與查詢

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

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

不生效情況

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

如果是 通過自定義的xml查詢,如果入?yún)⒂屑用茏⒔猓敲磿詣訉ψ侄芜M行加密處理 testMapper.listTest(testEntity)

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

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

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

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

總結(jié)

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

相關文章

最新評論

房山区| 青神县| 泰顺县| 北辰区| 松滋市| 安仁县| 思南县| 南靖县| 嘉祥县| 攀枝花市| 洱源县| 马尔康县| 肥城市| 广平县| 德令哈市| 广宗县| 蒲江县| 南皮县| 临湘市| 武夷山市| 呼伦贝尔市| 沙湾县| 正阳县| 日土县| 介休市| 大安市| 开化县| 贞丰县| 水城县| 海丰县| 抚松县| 香港 | 灵武市| 县级市| 咸阳市| 铜鼓县| 宁国市| 博罗县| 浦北县| 武隆县| 梅河口市|