欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Mybatis-Plus根據(jù)自定義注解實(shí)現(xiàn)自動(dòng)加解密的示例代碼

 更新時(shí)間:2024年06月20日 11:02:28   作者:太空眼睛  
我們把數(shù)據(jù)存到數(shù)據(jù)庫(kù)的時(shí)候,有些敏感字段是需要加密的,從數(shù)據(jù)庫(kù)查出來(lái)再進(jìn)行解密,如果我們使用的是Mybatis框架,那就跟著一起探索下如何使用框架的攔截器功能實(shí)現(xiàn)自動(dòng)加解密吧,需要的朋友可以參考下

背景

我們把數(shù)據(jù)存到數(shù)據(jù)庫(kù)的時(shí)候,有些敏感字段是需要加密的,從數(shù)據(jù)庫(kù)查出來(lái)再進(jìn)行解密。如果存在多張表或者多個(gè)地方需要對(duì)部分字段進(jìn)行加解密操作,每個(gè)地方都手寫一次加解密的動(dòng)作,顯然不是最好的選擇。如果我們使用的是Mybatis框架,那就跟著一起探索下如何使用框架的攔截器功能實(shí)現(xiàn)自動(dòng)加解密吧。

定義一個(gè)自定義注解

我們需要一個(gè)注解,只要實(shí)體類的屬性加上這個(gè)注解,那么就對(duì)這個(gè)屬性進(jìn)行自動(dòng)加解密。我們把這個(gè)注解定義靈活一點(diǎn),不僅可以放在屬性上,還可以放到類上,如果在類上使用這個(gè)注解,代表這個(gè)類的所有屬性都進(jìn)行自動(dòng)加密。

/**
 * 加密字段
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
public @interface EncryptField {

}

定義實(shí)體類

package com.wen3.demo.mybatisplus.po;

import com.baomidou.mybatisplus.annotation.*;
import com.wen3.demo.mybatisplus.encrypt.annotation.EncryptField;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;

@EncryptField
@Getter
@Setter
@Accessors(chain = true)
@KeySequence(value = "t_user_user_id_seq", dbType = DbType.POSTGRE_SQL)
@TableName("t_USER")
public class UserPo {

    /**
     * 用戶id
     */
    @TableId(value = "USER_ID", type = IdType.INPUT)
    private Long userId;

    /**
     * 用戶姓名
     */
    @TableField("USER_NAME")
    private String userName;

    /**
     * 用戶性別
     */
    @TableField("USER_SEX")
    private String userSex;

    /**
     * 用戶郵箱
     */
    @EncryptField
    @TableField("USER_EMAIL")
    private String userEmail;

    /**
     * 用戶賬號(hào)
     */
    @TableField("USER_ACCOUNT")
    private String userAccount;

    /**
     * 用戶地址
     */
    @TableField("USER_ADDRESS")
    private String userAddress;

    /**
     * 用戶密碼
     */
    @TableField("USER_PASSWORD")
    private String userPassword;

    /**
     * 用戶城市
     */
    @TableField("USER_CITY")
    private String userCity;

    /**
     * 用戶狀態(tài)
     */
    @TableField("USER_STATUS")
    private String userStatus;

    /**
     * 用戶區(qū)縣
     */
    @TableField("USER_SEAT")
    private String userSeat;
}

攔截器

Mybatis-Plus有個(gè)攔截器接口com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor,但發(fā)現(xiàn)這個(gè)接口有一些不足

  • 必須構(gòu)建一個(gè)MybatisPlusInterceptor這樣的Bean
  • 并調(diào)用這個(gè)BeanaddInnerInterceptor方法,把所有的InnerInterceptor加入進(jìn)去,才能生效
  • InnerInterceptor只有before攔截,缺省after攔截。加密可以在before里面完成,但解密需要在after里面完成,所以這個(gè)InnerInterceptor不能滿足我們的要求

所以繼續(xù)研究源碼,發(fā)現(xiàn)Mybatis有個(gè)org.apache.ibatis.plugin.Interceptor接口,這個(gè)接口能滿足我對(duì)自動(dòng)加解密的所有訴求

  • 首先,實(shí)現(xiàn)Interceptor接口,只要注冊(cè)成為Spring容器的Bean,攔截器就能生效
  • 可以更加靈活的在beforeafter之間插入自己的邏輯

加密攔截器

創(chuàng)建名為EncryptInterceptor的加密攔截器,對(duì)update操作進(jìn)行攔截,對(duì)帶@EncryptField注解的字段進(jìn)行加密處理,無(wú)論是save方法還是saveBatch方法都會(huì)被成功攔截到。

package com.wen3.demo.mybatisplus.encrypt.interceptor;

import com.wen3.demo.mybatisplus.encrypt.annotation.EncryptField;
import com.wen3.demo.mybatisplus.encrypt.util.FieldEncryptUtil;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Objects;

/**
 * 對(duì)update操作進(jìn)行攔截,對(duì){@link EncryptField}字段進(jìn)行加密處理;
 * 無(wú)論是save方法還是saveBatch方法都會(huì)被成功攔截;
 */
@Slf4j
@Intercepts({
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
@Component
public class EncryptInterceptor implements Interceptor {

    private static final String METHOD = "update";

    @Setter(onMethod_ = {@Autowired})
    private FieldEncryptUtil fieldEncryptUtil;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        if(!StringUtils.equals(METHOD, invocation.getMethod().getName())) {
            return invocation.proceed();
        }

        // 根據(jù)update攔截規(guī)則,第0個(gè)參數(shù)一定是MappedStatement,第1個(gè)參數(shù)是需要進(jìn)行判斷的參數(shù)
        Object param = invocation.getArgs()[1];
        if(Objects.isNull(param)) {
            return invocation.proceed();
        }

        // 加密處理
        fieldEncryptUtil.encrypt(param);

        return invocation.proceed();
    }
}

解密攔截器

創(chuàng)建名為DecryptInterceptor的加密攔截器,對(duì)query操作進(jìn)行攔截,對(duì)帶@EncryptField注解的字段進(jìn)行解密處理,無(wú)論是返回單個(gè)對(duì)象,還是對(duì)象的集合,都會(huì)被攔截到。

package com.wen3.demo.mybatisplus.encrypt.interceptor;

import cn.hutool.core.util.ClassUtil;
import com.wen3.demo.mybatisplus.encrypt.annotation.EncryptField;
import com.wen3.demo.mybatisplus.encrypt.util.FieldEncryptUtil;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.sql.Statement;
import java.util.Collection;

/**
 * 對(duì)query操作進(jìn)行攔截,對(duì){@link EncryptField}字段進(jìn)行解密處理;
 */
@Slf4j
@Intercepts({
        @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = Statement.class)
})
@Component
public class DecryptInterceptor implements Interceptor {

    private static final String METHOD = "query";

    @Setter(onMethod_ = {@Autowired})
    private FieldEncryptUtil fieldEncryptUtil;

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

        // 解密處理
        // 經(jīng)過測(cè)試發(fā)現(xiàn),無(wú)論是返回單個(gè)對(duì)象還是集合,result都是ArrayList類型
        if(ClassUtil.isAssignable(Collection.class, result.getClass())) {
            fieldEncryptUtil.decrypt((Collection) result);
        } else {
            fieldEncryptUtil.decrypt(result);
        }

        return result;
    }
}

加解密工具類

由于加密和解密絕大部分的邏輯是相似的,不同的地方在于

  • 加密需要通過反射處理的對(duì)象,是在SQL執(zhí)行前,是Invocation對(duì)象的參數(shù)列表中下標(biāo)為1的參數(shù);而解決需要通過反射處理的對(duì)象,是在SQL執(zhí)行后,對(duì)執(zhí)行結(jié)果對(duì)象進(jìn)行解密處理。
  • 一個(gè)是獲取到字段值進(jìn)行加密,一個(gè)是獲取到字段值進(jìn)行解密

于是把加解密邏輯抽象成一個(gè)工具類,把差異的部分做為參數(shù)傳入

package com.wen3.demo.mybatisplus.encrypt.util;

import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.ReflectUtil;
import com.wen3.demo.mybatisplus.encrypt.annotation.EncryptField;
import com.wen3.demo.mybatisplus.encrypt.service.FieldEncryptService;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.lang.reflect.Field;
import java.util.Collection;
import java.util.List;
import java.util.Objects;

/**
 * 加解密工具類
 */
@Slf4j
@Component
public class FieldEncryptUtil {

    @Setter(onMethod_ = {@Autowired})
    private FieldEncryptService fieldEncryptService;

    /**對(duì)EncryptField注解進(jìn)行加密處理*/
    public void encrypt(Object obj) {
        if(ClassUtil.isPrimitiveWrapper(obj.getClass())) {
            return;
        }
        encryptOrDecrypt(obj, true);
    }

    /**對(duì)EncryptField注解進(jìn)行解密處理*/
    public void decrypt(Object obj) {
        encryptOrDecrypt(obj, false);
    }

    /**對(duì)EncryptField注解進(jìn)行解密處理*/
    public void decrypt(Collection list) {
        if(CollectionUtils.isEmpty(list)) {
            return;
        }
        list.forEach(this::decrypt);
    }

    /**對(duì)EncryptField注解進(jìn)行加解密處理*/
    private void encryptOrDecrypt(Object obj, boolean encrypt) {
        // 根據(jù)update攔截規(guī)則,第0個(gè)參數(shù)一定是MappedStatement,第1個(gè)參數(shù)是需要進(jìn)行判斷的參數(shù)
        if(Objects.isNull(obj)) {
            return;
        }

        // 獲取所有帶加密注解的字段
        List<Field> encryptFields = null;
        // 判斷類上面是否有加密注解
        EncryptField encryptField = AnnotationUtils.findAnnotation(obj.getClass(), EncryptField.class);
        if(Objects.nonNull(encryptField)) {
            // 如果類上有加密注解,則所有字段都需要加密
            encryptFields = FieldUtils.getAllFieldsList(obj.getClass());
        } else {
            encryptFields = FieldUtils.getFieldsListWithAnnotation(obj.getClass(), EncryptField.class);
        }

        // 沒有字段需要加密,則跳過
        if(CollectionUtils.isEmpty(encryptFields)) {
            return;
        }

        encryptFields.forEach(f->{
            // 只支持String類型的加密
            if(!ClassUtil.isAssignable(String.class, f.getType())) {
                return;
            }

            String oldValue = (String) ReflectUtil.getFieldValue(obj, f);
            if(StringUtils.isBlank(oldValue)) {
                return;
            }

            String logText = null, newValue = null;
            if(encrypt) {
                logText = "encrypt";
                newValue = fieldEncryptService.encrypt(oldValue);
            } else {
                logText = "decrypt";
                newValue = fieldEncryptService.decrypt(oldValue);
            }

            log.info("{} success[{}=>{}]. before:{}, after:{}", logText, f.getDeclaringClass().getName(), f.getName(), oldValue, newValue);
            ReflectUtil.setFieldValue(obj, f, newValue);
        });
    }
}

加解密算法

Mybatis-Plus自帶了一個(gè)AES加解密算法的工具,我們只需要提供一個(gè)加密key,然后就可以完成一個(gè)加解密的業(yè)務(wù)處理了。

  • 先定義一個(gè)加解密接口
package com.wen3.demo.mybatisplus.encrypt.service;

/**
 * 數(shù)據(jù)加解密接口
 */
public interface FieldEncryptService {

    /**對(duì)數(shù)據(jù)進(jìn)行加密*/
    String encrypt(String value);

    /**對(duì)數(shù)據(jù)進(jìn)行解密*/
    String decrypt(String value);

    /**判斷數(shù)據(jù)是否憶加密*/
    default boolean isEncrypt(String value) {
        return false;
    }
}
  • 然后實(shí)現(xiàn)一個(gè)默認(rèn)的加解密實(shí)現(xiàn)類
package com.wen3.demo.mybatisplus.encrypt.service.impl;

import cn.hutool.core.util.ClassUtil;
import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import com.baomidou.mybatisplus.core.toolkit.AES;
import com.wen3.demo.mybatisplus.encrypt.service.FieldEncryptService;
import org.springframework.stereotype.Component;

import javax.crypto.IllegalBlockSizeException;

/**
 * 使用Mybatis-Plus自帶的AES加解密
 */
@Component
public class DefaultFieldEncryptService implements FieldEncryptService {

    private static final String ENCRYPT_KEY = "abcdefghijklmnop";

    @Override
    public String encrypt(String value) {
        if(isEncrypt(value)) {
            return value;
        }
        return AES.encrypt(value, ENCRYPT_KEY);
    }

    @Override
    public String decrypt(String value) {
        return AES.decrypt(value, ENCRYPT_KEY);
    }

    @Override
    public boolean isEncrypt(String value) {
        // 判斷是否已加密
        try {
            // 解密成功,說(shuō)明已加密
            decrypt(value);
            return true;
        } catch (MybatisPlusException e) {
            if(ClassUtil.isAssignable(IllegalBlockSizeException.class, e.getCause().getClass())) {
                return false;
            }
            throw e;
        }
    }
}

自動(dòng)加解密單元測(cè)試

package com.wen3.demo.mybatisplus.service;

import cn.hutool.core.util.RandomUtil;
import com.wen3.demo.mybatisplus.MybatisPlusSpringbootTestBase;
import com.wen3.demo.mybatisplus.encrypt.service.FieldEncryptService;
import com.wen3.demo.mybatisplus.po.UserPo;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.RandomStringUtils;
import org.junit.jupiter.api.Test;

import java.util.Collections;
import java.util.List;
import java.util.Map;

class UserServiceTest extends MybatisPlusSpringbootTestBase {

    @Resource
    private UserService userService;
    @Resource
    private FieldEncryptService fieldEncryptService;

    @Test
    void save() {
        UserPo userPo = new UserPo();
        String originalValue = RandomStringUtils.randomAlphabetic(16);
        String encryptValue = fieldEncryptService.encrypt(originalValue);
        userPo.setUserEmail(originalValue);
        userPo.setUserName(RandomStringUtils.randomAlphabetic(16));
        boolean testResult = userService.save(userPo);
        assertTrue(testResult);
        assertNotEquals(originalValue, userPo.getUserEmail());
        assertEquals(encryptValue, userPo.getUserEmail());

        // 測(cè)試解密: 返回單個(gè)對(duì)象
        UserPo userPoQuery = userService.getById(userPo.getUserId());
        assertEquals(originalValue, userPoQuery.getUserEmail());
        // 測(cè)試解密: 返回List
        List<UserPo> userPoList = userService.listByEmail(encryptValue);
        assertEquals(originalValue, userPoList.get(0).getUserEmail());

        // 測(cè)試saveBatch方法也會(huì)被攔截加密
        userPo.setUserId(null);
        testResult = userService.save(Collections.singletonList(userPo));
        assertTrue(testResult);
        assertNotEquals(originalValue, userPo.getUserEmail());
        assertEquals(encryptValue, userPo.getUserEmail());
    }
}

單元測(cè)試運(yùn)行截圖

以上就是Mybatis-Plus根據(jù)自定義注解實(shí)現(xiàn)自動(dòng)加解密的示例代碼的詳細(xì)內(nèi)容,更多關(guān)于Mybatis-Plus自定義注解加解密的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • springboot的yml配置文件通過db2的方式整合mysql的教程

    springboot的yml配置文件通過db2的方式整合mysql的教程

    這篇文章主要介紹了springboot的yml配置文件通過db2的方式整合mysql的教程,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-09-09
  • spring boot配置ssl實(shí)現(xiàn)HTTPS的方法

    spring boot配置ssl實(shí)現(xiàn)HTTPS的方法

    這篇文章主要介紹了spring boot配置ssl實(shí)現(xiàn)HTTPS的方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來(lái)看看吧
    2019-03-03
  • SpringCloud微服務(wù)熔斷器Hystrix使用詳解

    SpringCloud微服務(wù)熔斷器Hystrix使用詳解

    這篇文章主要介紹了Spring Cloud Hyxtrix的基本使用,它是Spring Cloud中集成的一個(gè)組件,在整個(gè)生態(tài)中主要為我們提供服務(wù)隔離,服務(wù)熔斷,服務(wù)降級(jí)功能,本文給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2022-07-07
  • Java中的傳值與傳引用實(shí)現(xiàn)過程解析

    Java中的傳值與傳引用實(shí)現(xiàn)過程解析

    這篇文章主要介紹了java中的傳值與傳引用實(shí)現(xiàn)過程解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-10-10
  • 在Java 8中將List轉(zhuǎn)換為Map對(duì)象方法

    在Java 8中將List轉(zhuǎn)換為Map對(duì)象方法

    這篇文章主要介紹了在Java 8中將List轉(zhuǎn)換為Map對(duì)象方法,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2018-11-11
  • springboot使用JPA時(shí)間類型進(jìn)行模糊查詢的方法

    springboot使用JPA時(shí)間類型進(jìn)行模糊查詢的方法

    這篇文章主要介紹了springboot使用JPA時(shí)間類型進(jìn)行模糊查詢的方法,需要的朋友可以參考下
    2018-03-03
  • Java中的阻塞隊(duì)列詳細(xì)介紹

    Java中的阻塞隊(duì)列詳細(xì)介紹

    這篇文章主要介紹了Java中的阻塞隊(duì)列詳細(xì)介紹的相關(guān)資料,需要的朋友可以參考下
    2016-11-11
  • Java?Calendar類使用之日期和時(shí)間處理指南

    Java?Calendar類使用之日期和時(shí)間處理指南

    這篇文章主要給大家介紹了關(guān)于Java?Calendar類使用之日期和時(shí)間處理指南的相關(guān)資料,Calendar類是Java中用于處理日期和時(shí)間的抽象類,它提供了一種獨(dú)立于特定日歷系統(tǒng)的方式來(lái)處理日期和時(shí)間,需要的朋友可以參考下
    2023-12-12
  • Spring?Cloud?Gateway整合sentinel?實(shí)現(xiàn)流控熔斷的問題

    Spring?Cloud?Gateway整合sentinel?實(shí)現(xiàn)流控熔斷的問題

    本文給大家介紹下?spring?cloud?gateway?如何整合?sentinel實(shí)現(xiàn)流控熔斷,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友一起看看吧
    2022-02-02
  • Mybatis中 XML配置詳解

    Mybatis中 XML配置詳解

    這篇文章主要介紹了Mybatis中 XML配置詳解的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下
    2017-01-01

最新評(píng)論