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

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

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

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

一、定義注解

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

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

import java.lang.annotation.*;

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

二、定義攔截器類

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

為我們創(chuàng)建的攔截器類打上@Component注解使之被Spring容器所管理;打上@Intercepts注解用于標識攔截器開始攔截的情況(執(zhí)行sql語句過程中的哪個位置)。Mybatis可以在執(zhí)行語句的過程中對特定對象進行攔截調(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ù)

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


這里選取ParameterHandler類型的setParameters方法,在每次執(zhí)行sql之前設(shè)置參數(shù)前進行攔截加密。整個攔截器類重寫intercept方法用于加密、重寫plugin方法用于將該攔截器接入攔截器鏈(這里可以選擇不重寫plugin方法,因為Interceptor類中定義的該方法默認內(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ù)庫之前對敏感數(shù)據(jù)加密
 * 場景:插入、更新時生效
 * 策略:
 *   - 在敏感字段所在實體類上添加@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ù)名稱,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,這里則能強轉(zhuǎn)為ResultSetHandler
            ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();
            // 獲取參數(shù)對像,即對應(yīng)MyBatis的 mapper 中 paramsType 的實例
            Field parameterField = parameterHandler.getClass().getDeclaredField("parameterObject");
            parameterField.setAccessible(true);
            // 取出參數(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即為待插入的實體對象
                    sensitiveObject = parameterObject;
                }
                // 獲取不到數(shù)據(jù)就直接放行
                if (Objects.isNull(sensitiveObject)) return invocation.proceed();
                // 校驗該實例的類是否被@SensitiveData所注解
                Class<?> sensitiveObjectClass = sensitiveObject.getClass();
                SensitiveData sensitiveData = AnnotationUtils.findAnnotation(sensitiveObjectClass, SensitiveData.class);
                if (Objects.nonNull(sensitiveData)) {
                    // 如果是被注解的類,則進行加密
                    // 取出當前當前類所有字段,傳入加密方法
                    Field[] declaredFields = sensitiveObjectClass.getDeclaredFields();
                    EncryptUtil.encrypt(declaredFields, sensitiveObject, EncryptUtil.ENCRYPT_MD5_MODE);
                }
            }
            return invocation.proceed();
        } catch (Exception e) {
            // 未作更多處理,加密失敗仍然會放行讓數(shù)據(jù)進入數(shù)據(jù)庫
            log.error("加密失敗", e);
        }
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        // 將這個攔截器接入攔截器鏈
        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);
            // 僅討論對字符串類型的字段的加密
            if (targetProperty instanceof String) {
                // 取得源字段值
                String value = (String) targetProperty;
                String valueEncrypt;
                if (ENCRYPT_MODE == ENCRYPT_MD5_MODE) {
                    // 使用MD5加密算法進行加密
                    valueEncrypt = EncryptUtil.MD5Encrypt(value);
                } else if (ENCRYPT_MODE == ENCRYPT_AES_MODE) {
                    // 使用AES加密算法進行加密
                    valueEncrypt = EncryptUtil.AESEncrypt(value);
                } else {
                    valueEncrypt = value;
                }
                // 將加密完成的字段值放入待用參數(shù)對象
                field.set(sensitiveObject, valueEncrypt);
            }
        }
    }
    return sensitiveObject;
}

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

如果選用的是雙向加密算法(可逆),還可以設(shè)計一個用于解密的攔截器類進行處理,選取ResultSetHandler類型的handleResultSets方法,在處理結(jié)果集之前進行解密,解密的情況根據(jù)具體需求來確定,可以基于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;
    }

    // 判斷是否是需要解密的敏感實體類
    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) {
        // 將這個攔截器接入攔截器鏈
        return Plugin.wrap(target, this);
    }

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

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

相關(guān)文章

最新評論