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

Mybatis敏感數(shù)據(jù)加解密插件實(shí)現(xiàn)過(guò)程

 更新時(shí)間:2026年01月06日 08:47:56   作者:dazhong2012  
本文介紹了基于MyBatis的敏感數(shù)據(jù)全鏈路安全處理方案,包括插件開發(fā)原理、具體實(shí)現(xiàn)代碼以及數(shù)據(jù)庫(kù)函數(shù)集成,通過(guò)MyBatis插件實(shí)現(xiàn)自動(dòng)加解密,結(jié)合注解和攔截器技術(shù),確保數(shù)據(jù)在數(shù)據(jù)庫(kù)操作的各個(gè)環(huán)節(jié)中的安全性

本文將詳細(xì)介紹基于MyBatis的敏感數(shù)據(jù)全鏈路安全處理方案,涵蓋插件開發(fā)原理、具體實(shí)現(xiàn)代碼以及數(shù)據(jù)庫(kù)函數(shù)集成。

1. MyBatis插件開發(fā)原理

1.1 插件的作用與攔截機(jī)制

MyBatis 插件是基于攔截器模式實(shí)現(xiàn)的,允許開發(fā)者在 MyBatis 執(zhí)行 SQL語(yǔ)句的關(guān)鍵節(jié)點(diǎn)插入自定義邏輯。MyBatis 支持對(duì)四大核心組件進(jìn)行攔截:

  • Executor:執(zhí)行器,用于攔截增刪改查操作
  • ParameterHandler:參數(shù)處理器,用于攔截參數(shù)設(shè)置
  • ResultSetHandler:結(jié)果集處理器,用于攔截結(jié)果集處理
  • StatementHandler:語(yǔ)句處理器,用于攔截SQL語(yǔ)句構(gòu)建
    插件通過(guò)實(shí)現(xiàn) Interceptor 接口并配合 @Intercepts@Signature注解來(lái)定義攔截規(guī)則。

1.2 插件的運(yùn)行機(jī)制與責(zé)任鏈模式

MyBatis的插件體系采用責(zé)任鏈模式,通過(guò)InterceptorChain管理所有攔截器。當(dāng)執(zhí)行目標(biāo)方法時(shí),會(huì)依次調(diào)用所有注冊(cè)的攔截器。具體實(shí)現(xiàn)機(jī)制如下:

// 插件動(dòng)態(tài)代理示例
public Object plugin(Object target) {
    if (target instanceof Executor) {
        return Plugin.wrap(target, this);
    }
    return target;
}

動(dòng)態(tài)代理機(jī)制確保在目標(biāo)方法執(zhí)行前后可以插入自定義邏輯,這是實(shí)現(xiàn)數(shù)據(jù)加解密的理論基礎(chǔ)。

2. MyBatis + 注解實(shí)現(xiàn)加解密插件

2.1 整體架構(gòu)設(shè)計(jì)

  • 本方案采用分層處理策略,將加解密與脫敏分離:
  • 數(shù)據(jù)訪問(wèn)層:通過(guò)MyBatis插件實(shí)現(xiàn)自動(dòng)加解密
  • API展示層:通過(guò)Jackson序列化器實(shí)現(xiàn)數(shù)據(jù)脫敏

2.2 注解定義

首先定義用于標(biāo)記敏感數(shù)據(jù)的注解:

// 類級(jí)注解,標(biāo)記需要加解密的實(shí)體
@Inherited
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface SensitiveData {
}

// 加解密字段注解
@Inherited
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface SensitiveField {
}

2.3 加密解密方法或函數(shù)工具類

以 pgsql 中的加解密函數(shù)作為舉例,實(shí)現(xiàn)加密解密方法調(diào)用:

@Component
public class PgCryptoUtil {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    /**
     * 加密方法 - 返回字符串格式(如十六進(jìn)制)
     */
    public String encrypt(String plainText, String key) throws Exception {
        String sql = "SELECT encode(pgp_sym_encrypt(?, ?), 'hex')";
        return jdbcTemplate.queryForObject(sql, String.class, plainText, key);
    }

    /**
     * 解密方法 - 接收字符串格式的加密數(shù)據(jù)
     */
    public String decrypt(String encryptedText, String key) throws Exception {
        // 假設(shè)encryptedText是十六進(jìn)制格式的字符串
        String sql = "SELECT pgp_sym_decrypt(decode(?, 'hex'), ?)";
        return jdbcTemplate.queryForObject(sql, String.class, encryptedText, key);
    }
}

2.4 基于攔截器的加解密實(shí)現(xiàn)

2.4.1 參數(shù)加密攔截器

攔截ParameterHandler.setParameters方法,在數(shù)據(jù)入庫(kù)前進(jìn)行加密。

// 加密攔截器 - 用于處理INSERT/UPDATE操作
@Component
@Intercepts({
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
public class EncryptInterceptor implements Interceptor {

    @Autowired
    private PgCryptoUtil pgCryptoUtil;

    private final String ENCRYPTION_KEY = "1234567890abcdef";

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object[] args = invocation.getArgs();
        Object parameter = args[1];

        // 加密處理
        if (parameter != null) {
            encryptParameters(parameter);
        }

        return invocation.proceed();
    }

    private void encryptParameters(Object parameter) {
        if (parameter instanceof List) {
            for (Object item : (List) parameter) {
                encryptItem(item);
            }
        } else {
            encryptItem(parameter);
        }
    }

    private void encryptItem(Object item) {
        if (item != null && item.getClass().isAnnotationPresent(SensitiveData.class)) {
            MetaObject metaObject = SystemMetaObject.forObject(item);
            Field[] fields = item.getClass().getDeclaredFields();

            for (Field field : fields) {
                if (field.isAnnotationPresent(SensitiveField.class) && field.getType() == String.class) {
                    String fieldName = field.getName();
                    Object value = metaObject.getValue(fieldName);

                    if (value instanceof String && !((String) value).isEmpty()) {
                        try {
                            // 加密字符串?dāng)?shù)據(jù)
                            String encryptedValue = pgCryptoUtil.encrypt((String) value, ENCRYPTION_KEY);
                            metaObject.setValue(fieldName, encryptedValue);
                        } catch (Exception e) {
                            throw new RuntimeException("加密字段 " + fieldName + " 時(shí)發(fā)生錯(cuò)誤", e);
                        }
                    }
                }
            }
        }
    }

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

    @Override
    public void setProperties(Properties properties) {
    }
}

2.4.2 結(jié)果集解密攔截器

攔截ResultSetHandler.handleResultSets方法,在數(shù)據(jù)查詢后進(jìn)行解密

// 解密攔截器 - 用于處理SELECT操作(您的代碼優(yōu)化版)
@Component
@Intercepts({
        @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})
})
public class DecryptInterceptor implements Interceptor {

    @Autowired
    private PgCryptoUtil pgCryptoUtil;

    private final String ENCRYPTION_KEY = "1234567890abcdef";

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object result = invocation.proceed();

        if (result == null) {
            return null;
        }

        if (result instanceof List) {
            for (Object item : (List) result) {
                decryptItem(item);
            }
        } else {
            decryptItem(result);
        }

        return result;
    }

    private void decryptItem(Object item) {
        if (item != null && item.getClass().isAnnotationPresent(SensitiveData.class)) {
            MetaObject metaObject = SystemMetaObject.forObject(item);
            Field[] fields = item.getClass().getDeclaredFields();

            for (Field field : fields) {
                if (field.isAnnotationPresent(SensitiveField.class) && field.getType() == String.class) {
                    String fieldName = field.getName();
                    Object value = metaObject.getValue(fieldName);

                    // 關(guān)鍵修改:處理String類型的加密數(shù)據(jù)
                    if (value instanceof String && !((String) value).isEmpty()) {
                        try {
                            String decryptedValue = pgCryptoUtil.decrypt((String) value, ENCRYPTION_KEY);
                            metaObject.setValue(fieldName, decryptedValue);
                        } catch (Exception e) {
                            // 記錄解密失敗但不要拋出異常,以免影響正常查詢
                            System.err.println("解密字段 " + fieldName + " 失敗: " + e.getMessage());
                            // 可以選擇保留原始加密值或設(shè)置為null
                            // metaObject.setValue(fieldName, null);
                        }
                    }
                }
            }
        }
    }

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

    @Override
    public void setProperties(Properties properties) {
    }
}

2.5 實(shí)體類使用

@SensitiveData 
@Data // lombok注解
public class UserInfo extends BaseEntity implements Serializable {

    /**
     * 主鍵
     */
    private String id;

    /**
     *手機(jī)號(hào)
     */
    @SensitiveField
    @Excel(name = "手機(jī)號(hào)")
    private String phone;
}

3. 數(shù)據(jù)庫(kù)層面加解密函數(shù)

3.1 MySQL加解密函數(shù)

MySQL原生支持AES加解密函數(shù),可在SQL層面實(shí)現(xiàn)加解密:

-- 加密數(shù)據(jù)插入
INSERT INTO users (username, phone_encrypted) 
VALUES ('john_doe', AES_ENCRYPT('13812345678', 'encryption_key'));

-- 查詢解密數(shù)據(jù)
SELECT username, AES_DECRYPT(phone_encrypted, 'encryption_key') as phone 
FROM users WHERE username = 'john_doe';

3.2 PostgreSQL加解密函數(shù)

PostgreSQL通過(guò)pgcrypto擴(kuò)展提供加密功能:

-- 啟用pgcrypto擴(kuò)展
CREATE EXTENSION IF NOT EXISTS pgcrypto;

-- 加密數(shù)據(jù)插入
INSERT INTO users (username, phone_encrypted) 
VALUES ('john_doe', pgp_sym_encrypt('13812345678', 'encryption_key'));

-- 查詢解密數(shù)據(jù)
SELECT username, pgp_sym_decrypt(phone_encrypted, 'encryption_key') as phone 
FROM users WHERE username = 'john_doe';

-- 使用自定義函數(shù)簡(jiǎn)化操作
CREATE FUNCTION encrypt_value(value TEXT, key TEXT) 
RETURNS TEXT AS $$
BEGIN
    RETURN encode(pgp_sym_encrypt(value, key), 'base64');
END;
$$ LANGUAGE plpgsql;

單純?cè)?PostgreSQL 中使用更安全的加密方式,推薦使用其默認(rèn)的 PGP 函數(shù)。注意:此方法與 MySQL 默認(rèn)加密不兼容。

-- 加密 (使用默認(rèn)更安全的CBC模式等選項(xiàng))
SELECT armor(pgp_sym_encrypt('hello', 'mykey')) AS encrypted_data_pgp;
-- 解密
SELECT pgp_sym_decrypt(dearmor('<上述armor函數(shù)輸出的結(jié)果>'), 'mykey') AS decrypted_data_pgp;

3.3 支持 mysql和 pgsql 的通用函數(shù)定義

由于兩種數(shù)據(jù)庫(kù)默認(rèn)的加密模式不同,為了實(shí)現(xiàn)兼容,核心思路是在 PostgreSQL 端使用 encrypt和 decrypt函數(shù),并明確指定加密參數(shù),以模擬 MySQL 的行為。

以下示例均使用明文 hello和密鑰 mykey。

1. MySQL 加解密示例

MySQL 使用默認(rèn)的 AES-128-ECB 模式。

-- 加密 (結(jié)果為二進(jìn)制, 通常用 HEX 轉(zhuǎn)換為十六進(jìn)制字符串存儲(chǔ)或查看)
SELECT HEX(AES_ENCRYPT('hello', 'mykey')) AS encrypted_data;
-- 結(jié)果示例: '744661B08EB4698B64DE167B7B43BCD7'

-- 解密 (需先將十六進(jìn)制字符串轉(zhuǎn)換回二進(jìn)制)
SELECT AES_DECRYPT(UNHEX('744661B08EB4698B64DE167B7B43BCD7'), 'mykey') AS decrypted_data;
-- 結(jié)果: 'hello'

2. PostgreSQL 兼容 MySQL 的加解密示例

在 PostgreSQL 中,使用 pgcrypto擴(kuò)展的 encrypt/decrypt函數(shù),并指定算法和模式為 aes-ecb以達(dá)到兼容。

-- 首先確保啟用 pgcrypto 擴(kuò)展
CREATE EXTENSION IF NOT EXISTS pgcrypto;

-- 加密 (使用 'aes-ecb' 模式模擬MySQL,結(jié)果轉(zhuǎn)換為十六進(jìn)制以便比較和存儲(chǔ))
SELECT encode(encrypt('hello', 'mykey', 'aes-ecb'), 'hex') AS encrypted_data;
-- 結(jié)果應(yīng)與MySQL一致: '744661B08EB4698B64DE167B7B43BCD7'

-- 解密
SELECT convert_from(decrypt(decode('744661B08EB4698B64DE167B7B43BCD7', 'hex'), 'mykey', 'aes-ecb'), 'utf8') AS decrypted_data;
-- 結(jié)果: 'hello'

四、相關(guān)開源項(xiàng)目 mybatis-encrypt-plugin 介紹

mybatis-encrypt-plugin 是一款開源的基于mybatis的插件機(jī)制編寫的一套敏感數(shù)據(jù)加解密以及數(shù)據(jù)脫敏工具。

開源地址:https://github.com/andydazhong/mybatis-encrypt-plugin

1. 實(shí)現(xiàn)原理

  • 攔截mybatis的StatementHandler 對(duì)讀寫請(qǐng)求進(jìn)行脫敏和字段的加密。
  • 攔截mybatis的ResultSetHandler,對(duì)讀請(qǐng)求的響應(yīng)進(jìn)行加密字段的解密賦值。

2. 使用方式

1. 導(dǎo)入依賴

 <!-- mybatis數(shù)據(jù)脫敏插件 -->
    <dependency>
        <groupId>com.github.chenhaiyangs</groupId>
        <artifactId>mybatis-encrypt-plugin</artifactId>
        <version>1.0.0</version>
    </dependency>

2. 編寫加解密實(shí)現(xiàn)類以及配置mybatis的插件

下面在springboot場(chǎng)景下的一個(gè)配置案例。

 /**
     * 插件配置
     */
    @Configuration
    public class EncryptPluginConfig {
    
        //加密方式
        @Bean
        Encrypt encryptor() throws Exception{
            return new AesSupport("1870577f29b17d6787782f35998c4a79");
        }
        
        //配置插件
        @Bean
        ConfigurationCustomizer configurationCustomizer() throws Exception{
            DecryptReadInterceptor decryptReadInterceptor = new DecryptReadInterceptor(encryptor());
            SensitiveAndEncryptWriteInterceptor sensitiveAndEncryptWriteInterceptor = new SensitiveAndEncryptWriteInterceptor(encryptor());
    
            return (configuration) -> {
                configuration.addInterceptor(decryptReadInterceptor);
                configuration.addInterceptor(sensitiveAndEncryptWriteInterceptor);
            };
        }
    }

3. 在vo類上添加功能注解使得插件生效

@SensitiveEncryptEnabled
@Data
public class UserDTO {

   private Integer id;
    /**
     * 用戶名
     */
    @EncryptField
    private String userName;
    /**
     * 脫敏的用戶名
     */
    @SensitiveField(SensitiveType.CHINESE_NAME)
    private String userNameSensitive;
    /**
     * 值的賦值不從數(shù)據(jù)庫(kù)取,而是從userName字段獲得。
     */
    @SensitiveBinded(bindField = "userName",value = SensitiveType.CHINESE_NAME)
    private String userNameOnlyDTO;
    /**
     * 身份證號(hào)
     */
    @EncryptField
    private String idcard;
    /**
     * 脫敏的身份證號(hào)
     */
    @SensitiveField(SensitiveType.ID_CARD)
    private String idcardSensitive;
    /**
     * 一個(gè)json串,需要脫敏
     * SensitiveJSONField標(biāo)記json中需要脫敏的字段
     */
    @SensitiveJSONField(sensitivelist = {
            @SensitiveJSONFieldKey(key = "idcard",type = SensitiveType.ID_CARD),
            @SensitiveJSONFieldKey(key = "username",type = SensitiveType.CHINESE_NAME),
    })
    private String jsonStr;

    private int age;

    @SensitiveField(SensitiveType.EMAIL)
    private String email;
}

總結(jié)

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

相關(guān)文章

  • Java中泛型的使用和優(yōu)點(diǎn)解析

    Java中泛型的使用和優(yōu)點(diǎn)解析

    這篇文章主要介紹了Java中泛型的使用和優(yōu)點(diǎn)解析,泛型使用過(guò)程中,操作的數(shù)據(jù)類型被指定為一個(gè)參數(shù),這種參數(shù)類型可以用在類、接口和方法中,分別被稱為泛型類、泛型接口、泛型方法,需要的朋友可以參考下
    2023-09-09
  • MyBatis的?級(jí)映射及延遲加載過(guò)程詳解

    MyBatis的?級(jí)映射及延遲加載過(guò)程詳解

    這篇文章主要介紹了MyBatis的?級(jí)映射及延遲加載,包括多對(duì)一延時(shí)加載方式及一對(duì)多,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2023-02-02
  • SpringSecurity自定義登錄接口的實(shí)現(xiàn)

    SpringSecurity自定義登錄接口的實(shí)現(xiàn)

    本文介紹了使用Spring Security實(shí)現(xiàn)自定義登錄接口,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2025-01-01
  • MyBatisPlus?大數(shù)據(jù)量查詢慢的問(wèn)題解決

    MyBatisPlus?大數(shù)據(jù)量查詢慢的問(wèn)題解決

    本文主要介紹了MyBatis?Plus?解決大數(shù)據(jù)量查詢慢問(wèn)題,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-02-02
  • idea更改項(xiàng)目(模塊)JDK版本的操作步驟

    idea更改項(xiàng)目(模塊)JDK版本的操作步驟

    idea很多地方都設(shè)置了jdk版本,不同模塊的jdk版本也可能不一樣,下面這篇文章主要給大家介紹了關(guān)于idea更改項(xiàng)目(模塊)JDK版本的操作步驟,文中通過(guò)圖文介紹的非常詳細(xì),需要的朋友可以參考下
    2023-11-11
  • Spring Boot使用Druid連接池的示例代碼

    Spring Boot使用Druid連接池的示例代碼

    Druid是Java語(yǔ)言中最好的數(shù)據(jù)庫(kù)連接池。這篇文章主要介紹了Spring Boot使用Druid連接池的示例代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-06-06
  • Java中關(guān)于String的全面解析

    Java中關(guān)于String的全面解析

    這篇文章主要介紹了Java中關(guān)于String全面解析,下面我們來(lái)一起學(xué)習(xí)一下吧
    2019-05-05
  • Java/Android 獲取網(wǎng)絡(luò)重定向文件的真實(shí)URL的示例代碼

    Java/Android 獲取網(wǎng)絡(luò)重定向文件的真實(shí)URL的示例代碼

    本篇文章主要介紹了Java/Android 獲取網(wǎng)絡(luò)重定向文件的真實(shí)URL的示例代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-11-11
  • springmvc實(shí)現(xiàn)跨服務(wù)器文件上傳功能

    springmvc實(shí)現(xiàn)跨服務(wù)器文件上傳功能

    這篇文章主要為大家詳細(xì)介紹了springmvc實(shí)現(xiàn)跨服務(wù)器文件上傳功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-08-08
  • java常見的字符串拼接方式總結(jié)

    java常見的字符串拼接方式總結(jié)

    這篇文章主要為大家詳細(xì)介紹了java中常見的字符串拼接方式,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2023-09-09

最新評(píng)論

佛坪县| 高台县| 蚌埠市| 深圳市| 遂宁市| 巫山县| 湖南省| 山西省| 太仓市| 繁峙县| 大宁县| 萨嘎县| 安平县| 河津市| 曲松县| 陆河县| 城口县| 修武县| 商丘市| 杭锦旗| 化隆| 兴宁市| 绥阳县| 随州市| 新巴尔虎右旗| 新丰县| 黄骅市| 绵竹市| 芦溪县| 三江| 博白县| 永善县| 德保县| 神池县| 儋州市| 滦南县| 新宁县| 克什克腾旗| 石屏县| 华池县| 自贡市|