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

MyBatis-Plus攔截器對(duì)敏感數(shù)據(jù)實(shí)現(xiàn)加密

 更新時(shí)間:2021年11月09日 09:04:02   作者:csu_cangkui  
做課程項(xiàng)目petstore時(shí)遇到需要加密屬性的問(wèn)題,而MyBatis-Plus為開(kāi)發(fā)者提供了攔截器的相關(guān)接口,本文主要介紹通過(guò)MyBatis-Plus的攔截器接口自定義一個(gè)攔截器類實(shí)現(xiàn)敏感數(shù)據(jù)如用戶密碼的加密功能,感興趣的可以了解一下

做課程項(xiàng)目petstore時(shí)遇到需要加密屬性的問(wèn)題,而MyBatis-Plus為開(kāi)發(fā)者提供了攔截器的相關(guān)接口,用于與數(shù)據(jù)庫(kù)交互的過(guò)程中實(shí)現(xiàn)特定功能,本文主要介紹通過(guò)MyBatis-Plus的攔截器接口自定義一個(gè)攔截器類實(shí)現(xiàn)敏感數(shù)據(jù)如用戶密碼的加密功能,即實(shí)現(xiàn)在DAO層寫入數(shù)據(jù)庫(kù)時(shí)傳入明文,而數(shù)據(jù)庫(kù)中存儲(chǔ)的是密文。由于加密算法有多種,這里不展示具體的加密步驟,主要討論攔截器的構(gòu)建。

一、定義注解

自定義相關(guān)注解,將需要加密的字段及其所在的實(shí)體類進(jìn)行標(biāo)注,方便攔截器攔截時(shí)的判斷。這里定義了兩個(gè)注解(分別形成兩個(gè)不同的文件),@SensitiveData和@SensitiveField,分別用于注解實(shí)體類、實(shí)體類中需要加密的屬性。注意,注解@Target內(nèi)ElementType取值為TYPE時(shí)表示該注解用于注解類,取值為FIELD表示該注解用于注解類的屬性。

package org.csu.mypetstore.api.utils.encrypt.annotation;

import java.lang.annotation.*;

@Inherited
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface SensitiveData {
}

二、定義攔截器類

自定義的攔截器類需要實(shí)現(xiàn)MyBatis的Interceptor類,主要是重寫三個(gè)方法public Object intercept(Invocation invocation)、public Object plugin(Object target)、public void setProperties(Properties properties)。這三個(gè)方法前兩個(gè)我們需要使用到,第三個(gè)方法這里暫時(shí)用不到。

為我們創(chuàng)建的攔截器類打上@Component注解使之被Spring容器所管理;打上@Intercepts注解用于標(biāo)識(shí)攔截器開(kāi)始攔截的情況(執(zhí)行sql語(yǔ)句過(guò)程中的哪個(gè)位置)。Mybatis可以在執(zhí)行語(yǔ)句的過(guò)程中對(duì)特定對(duì)象進(jìn)行攔截調(diào)用,主要有以下四種情況:

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed) 處理增刪改查
  • ParameterHandler (getParameterObject, setParameters) 設(shè)置預(yù)編譯參數(shù)
  • ResultSetHandler (handleResultSets, handleOutputParameters) 處理結(jié)果
  • StatementHandler (prepare, parameterize, batch, update, query) 處理sql預(yù)編譯,設(shè)置參數(shù)

通過(guò)@Intercepts注解內(nèi)部的@Signature注解進(jìn)行配置,有三個(gè)配置選項(xiàng)分別為type、method、args,type用于指定上述四類中的某一類,method用于指定該類型中的哪個(gè)方法執(zhí)行時(shí)被攔截,args用于接收被攔截方法的參數(shù)。觀察注解@Signature的代碼可以加深理解:


這里選取ParameterHandler類型的setParameters方法,在每次執(zhí)行sql之前設(shè)置參數(shù)前進(jìn)行攔截加密。整個(gè)攔截器類重寫intercept方法用于加密、重寫plugin方法用于將該攔截器接入攔截器鏈(這里可以選擇不重寫plugin方法,因?yàn)镮nterceptor類中定義的該方法默認(rèn)內(nèi)容與我們重寫的內(nèi)容是一樣的),代碼如下:

package org.csu.mypetstore.api.utils.encrypt;

import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.plugin.*;
import org.csu.mypetstore.api.utils.encrypt.annotation.SensitiveData;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.sql.PreparedStatement;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;

/**
 * 加密攔截器:插入數(shù)據(jù)庫(kù)之前對(duì)敏感數(shù)據(jù)加密
 * 場(chǎng)景:插入、更新時(shí)生效
 * 策略:
 *   - 在敏感字段所在實(shí)體類上添加@SensitiveData注解
 *   - 在敏感字段上添加@SensitiveField注解
 *
 * @author csu_cangkui
 * @date 2021/8/14
 */
@Slf4j
@Component
@Intercepts({
        @Signature(type = ParameterHandler.class, method = "setParameters", args = PreparedStatement.class)
})
public class EncryptInterceptor implements Interceptor {

    // 更新時(shí)的參數(shù)名稱,ParamMap的key值
    private static final String CRYPT = "et";

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        try {
            // @Signature 指定了 type= parameterHandler.class 后,這里的 invocation.getTarget() 便是parameterHandler
            // 若指定ResultSetHandler,這里則能強(qiáng)轉(zhuǎn)為ResultSetHandler
            ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();
            // 獲取參數(shù)對(duì)像,即對(duì)應(yīng)MyBatis的 mapper 中 paramsType 的實(shí)例
            Field parameterField = parameterHandler.getClass().getDeclaredField("parameterObject");
            parameterField.setAccessible(true);
            // 取出參數(shù)實(shí)例
            Object parameterObject = parameterHandler.getParameterObject();
            if (parameterObject != null) {
                Object sensitiveObject = null;
                if (parameterObject instanceof MapperMethod.ParamMap) {
                    // 更新操作被攔截
                    Map paramMap = (Map) parameterObject;
                    sensitiveObject = paramMap.get(CRYPT);
                } else {
                    // 插入操作被攔截,parameterObject即為待插入的實(shí)體對(duì)象
                    sensitiveObject = parameterObject;
                }
                // 獲取不到數(shù)據(jù)就直接放行
                if (Objects.isNull(sensitiveObject)) return invocation.proceed();
                // 校驗(yàn)該實(shí)例的類是否被@SensitiveData所注解
                Class<?> sensitiveObjectClass = sensitiveObject.getClass();
                SensitiveData sensitiveData = AnnotationUtils.findAnnotation(sensitiveObjectClass, SensitiveData.class);
                if (Objects.nonNull(sensitiveData)) {
                    // 如果是被注解的類,則進(jìn)行加密
                    // 取出當(dāng)前當(dāng)前類所有字段,傳入加密方法
                    Field[] declaredFields = sensitiveObjectClass.getDeclaredFields();
                    EncryptUtil.encrypt(declaredFields, sensitiveObject, EncryptUtil.ENCRYPT_MD5_MODE);
                }
            }
            return invocation.proceed();
        } catch (Exception e) {
            // 未作更多處理,加密失敗仍然會(huì)放行讓數(shù)據(jù)進(jìn)入數(shù)據(jù)庫(kù)
            log.error("加密失敗", e);
        }
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        // 將這個(gè)攔截器接入攔截器鏈
        return Plugin.wrap(target, this);
    }

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

EncryptUtil類的encrypt加密方法的大致處理流程:

static <T> T encrypt(Field[] declaredFields, T sensitiveObject, final int ENCRYPT_MODE) throws IllegalAccessException {
    for (Field field : declaredFields) {
        // 取出所有被SensitiveField注解的字段
        SensitiveField sensitiveField = field.getAnnotation(SensitiveField.class);
        if (Objects.nonNull(sensitiveField)) {
            field.setAccessible(true);
            Object targetProperty = field.get(sensitiveObject);
            // 僅討論對(duì)字符串類型的字段的加密
            if (targetProperty instanceof String) {
                // 取得源字段值
                String value = (String) targetProperty;
                String valueEncrypt;
                if (ENCRYPT_MODE == ENCRYPT_MD5_MODE) {
                    // 使用MD5加密算法進(jìn)行加密
                    valueEncrypt = EncryptUtil.MD5Encrypt(value);
                } else if (ENCRYPT_MODE == ENCRYPT_AES_MODE) {
                    // 使用AES加密算法進(jìn)行加密
                    valueEncrypt = EncryptUtil.AESEncrypt(value);
                } else {
                    valueEncrypt = value;
                }
                // 將加密完成的字段值放入待用參數(shù)對(duì)象
                field.set(sensitiveObject, valueEncrypt);
            }
        }
    }
    return sensitiveObject;
}

構(gòu)建過(guò)程中主要是使用了Java的反射機(jī)制來(lái)處理。在構(gòu)建的過(guò)程中需要注意的一點(diǎn)是,我們需要在更新、插入時(shí)進(jìn)行加密,但是網(wǎng)上很多方法都是默認(rèn)為插入時(shí)加密,所以取出來(lái)的parameterObject對(duì)象都是默認(rèn)為這個(gè)表對(duì)應(yīng)的實(shí)體類。但是更新操作不同,更新操作時(shí)打印parameterObject對(duì)象的類型可看到是org.apache.ibatis.binding.MapperMethod$ParamMap,即取出的parameterObject對(duì)象是一個(gè)ParamMap類,而其中"et"的key對(duì)應(yīng)的value才是我們需要的實(shí)體類,因此這里需要通過(guò)判斷parameterObject對(duì)象的類型來(lái)分類進(jìn)行處理。

如果選用的是雙向加密算法(可逆),還可以設(shè)計(jì)一個(gè)用于解密的攔截器類進(jìn)行處理,選取ResultSetHandler類型的handleResultSets方法,在處理結(jié)果集之前進(jìn)行解密,解密的情況根據(jù)具體需求來(lái)確定,可以基于selectList方法、基于selectOne方法等等。

package org.csu.mypetstore.api.utils.encrypt;

import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.plugin.*;
import org.csu.mypetstore.api.utils.encrypt.annotation.SensitiveData;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;

import java.beans.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Properties;

@Slf4j
@Component
@Intercepts({
        @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = Statement.class)
})
public class DecryptInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object resultObject = invocation.proceed();
        try {
            if (Objects.isNull(resultObject)) return null;
            if (resultObject instanceof ArrayList) {
                // 基于selectList
                List resultList = (ArrayList) resultObject;
                if (!resultList.isEmpty() && needToDecrypt(resultList.get(0))) {
                    for (Object result : resultList) {
                        //逐一解密
                        EncryptUtils.decrypt(result);
                    }
                }
            } else {
                // 基于selectOne
                if (needToDecrypt(resultObject)) EncryptUtils.decrypt((String) resultObject);
            }
            return resultObject;
        } catch (Exception e) {
            log.error("解密失敗", e);
        }
        return resultObject;
    }

    // 判斷是否是需要解密的敏感實(shí)體類
    private boolean needToDecrypt(Object object) {
        Class<?> objectClass = object.getClass();
        SensitiveData sensitiveData = AnnotationUtils.findAnnotation(objectClass, SensitiveData.class);
        return Objects.nonNull(sensitiveData);
    }

    @Override
    public Object plugin(Object target) {
        // 將這個(gè)攔截器接入攔截器鏈
        return Plugin.wrap(target, this);
    }

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

到此這篇關(guān)于MyBatis-Plus攔截器對(duì)敏感數(shù)據(jù)實(shí)現(xiàn)加密的文章就介紹到這了,更多相關(guān)MyBatis-Plus攔截器對(duì)敏感數(shù)據(jù)加密內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論