Mybatisplus は、保存暗号化、出力復号化、ファジー クエリを実装するようにインターセプターを構成します。

前書き: 企業のニーズにより、特定のエンティティ クラスの特定のフィールド値を暗号化して保存する必要があり、クエリ中にプレーン テキスト出力を復号化できます。録音には 2 つの方法があります。

1. 最初の方法:

(1) @TableField(typeHandler = TypeHandler.class) を使用して、付属のフィールド型ハンドラーにアノテーションを付け、BaseTypeHandler を継承するハンドル クラスを記述します。これ自体は、リストへのマップなどのフィールド タイプの変換を処理するために使用されます。また、値の暗号化にも使用できます。

注意: このメソッドを使用するには前提条件があります。クエリを有効にするには、@TableName 注釈を追加する必要がありますautoResultMap = true。それ以外の場合は、新しい変更のみが有効になり、インターセプトされます。ソースコードを見ずに、エンティティ全体が存在するかどうかを確認してからautoResultMap = true、フィールドのインターセプト処理を 1 つずつ実行して効率を最適化する必要があると思います。

@Data
@TableName(value = "xxx",autoResultMap = true)
public class xxx implements Serializable {
    
    

    private static final long serialVersionUID = 1L;

    @TableId(type = IdType.NONE, value = "id")
    private String id;

    @ApiModelProperty(value = "用户名")
    @TableField(value = "username",typeHandler = TypeControlHandler.class)
    private String username;

    @ApiModelProperty(value = "手机号")
    @TableField(value = "phone",typeHandler = TypeControlHandler.class)
    private String phone;
}

AES_ENCRYPT(2) 暗号化および復号化方法の定義 - カスタマイズ (ここでは mysqlと関数の暗号化および復号化が使用されますAES_DECRYPT。主にファジー クエリを実現するために、特定の暗号化および復号化方法はカスタマイズできます)

//需要导包:
//<dependency>
//	<groupId>commons-codec</groupId>
//	<artifactId>commons-codec</artifactId>
//</dependency>


import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;

@Slf4j
public class DBAesUtils {
    
    
/** AES 加解密密钥,请勿擅自修改!!! */
    public static final String key = "唯一加解密密匙key-自定义";


    /**
     * AES 加密 使用AES-128-ECB加密模式
     * @param sSrc  需要加密的字段
     * @param sKey  16 位密钥
     * @return
     * @throws Exception
     */
    public static String Encrypt(String sSrc, String sKey) {
    
    
        try {
    
    
            if (sKey == null) {
    
    
                return null;
            }
            /** 判断Key是否为16位 */
            if (sKey.length() != 16) {
    
    
                return null;
            }
            byte[] raw = sKey.getBytes("utf-8");
            SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
            /** "算法/模式/补码方式" */
            Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
            cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
            byte[] encrypted = cipher.doFinal(sSrc.getBytes("utf-8"));
            /** 此处使用BASE64做转码功能,同时能起到2次加密的作用。 */
            return new Base64().encodeToString(encrypted);
        } catch (Exception e) {
    
    
            e.printStackTrace();
            return null;
        }
    }

    public static String Encrypt(String sSrc) {
    
    
        return Encrypt(sSrc, key);
    }

    /**
     * AES 解密 使用AES-128-ECB加密模式
     * @param sSrc  需要解密的字段
     * @param sKey  16 位密钥
     * @return
     * @throws Exception
     */
    public static String Decrypt(String sSrc, String sKey) {
    
    
        try {
    
    
            // 判断Key是否正确
            if (sKey == null) {
    
    
                return null;
            }
            // 判断Key是否为16位
            if (sKey.length() != 16) {
    
    
                return null;
            }
            byte[] raw = sKey.getBytes("utf-8");
            SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
            Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
            cipher.init(Cipher.DECRYPT_MODE, skeySpec);
            /** 先用base64解密 */
            byte[] encrypted1 = new Base64().decode(sSrc);
            try {
    
    
                byte[] original = cipher.doFinal(encrypted1);
                String originalString = new String(original,"utf-8");
                return originalString;
            } catch (Exception e) {
    
    
                System.out.println(e.toString());
                return null;
            }
        } catch (Exception e) {
    
    
            e.printStackTrace();
            return null;
        }
    }


    public static String Decrypt(String sSrc) {
    
    
        return Decrypt(sSrc, key);
    }
  }

(3) 具体的な傍受処理実装の定義

import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class TypeControlHandler extends BaseTypeHandler<String> {
    
    
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
    
    
        ps.setString(i, DBAesUtils.Encrypt(parameter));
    }

    @Override
    public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
    
    
        return DBAesUtils.Decrypt(rs.getString(columnName));
    }

    @Override
    public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
    
    
        return DBAesUtils.Decrypt(rs.getString(columnIndex));
    }

    @Override
    public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
    
    
        return DBAesUtils.Decrypt(cs.getString(columnIndex));
    }

}

(4) 最終的な実装結果(ちょっとした問題は、暗号化前の文字が同じであれば、暗号化された暗号文も同じになってしまうこと) 暗号化フィールドのクエリに関しては、良い方法が思いつきませんでした
ここに画像の説明を挿入します
。今考えられるのは、それを使用せずにハードコードされた条件を使用することです。クエリを実行するには、ここでは mysql の暗号化および復号化関数が使用されます。他のデータベースは、使用する前に暗号化および復号化方法を変更する必要がありますLambdaQueryWrapperQueryWrapper

wrapper.like("AES_DECRYPT(FROM_BASE64(字段名),'" + key + "')", 查询值);

2 番目の方法 (実際、実装原則は最初の方法ですが、比較的柔軟な拡張機能が付いています):

(1) アノテーションを2つ定義する

/**
 * 需要加解密的字段用这个注解
 * @author [email protected]
 * @Date 2023-05-31
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface EncryptedColumn {
    
    

}

/**
 * 需要加解密的实体类用这个注解
 * @author [email protected]
 * @Date 2023-05-31
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface EncryptedTable {
    
    

}

(2)InnerInterceptor傍受暗号化操作の実装(DBAesUtils暗号化には最初のメソッドのツールクラスを使用)

import cn.hutool.core.util.ReflectUtil;
import com.baomidou.mybatisplus.core.conditions.AbstractWrapper;
import com.baomidou.mybatisplus.core.conditions.update.Update;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.extension.parser.JsqlParserSupport;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.springframework.core.annotation.AnnotationUtils;

import java.lang.reflect.Field;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@SuppressWarnings({
    
    "rawtypes"})
public class EncryptInterceptor extends JsqlParserSupport implements InnerInterceptor {
    
    
 /**
     * 变量占位符正则
     */
    private static final Pattern PARAM_PAIRS_RE = Pattern.compile("#\\{ew\\.paramNameValuePairs\\.(" + Constants.WRAPPER_PARAM + "\\d+)\\}");

    @Override
    public void beforeUpdate(Executor executor, MappedStatement mappedStatement, Object parameterObject) throws SQLException {
    
    
        if (Objects.isNull(parameterObject)) {
    
    
            return;
        }
        // 通过MybatisPlus自带API(save、insert等)新增数据库时
        if (!(parameterObject instanceof Map)) {
    
    
            if (needToDecrypt(parameterObject.getClass())) {
    
    
                encryptEntity(parameterObject);
            }
            return;
        }
        Map paramMap = (Map) parameterObject;
        Object param;
        // 通过MybatisPlus自带API(update、updateById等)修改数据库时
        if (paramMap.containsKey(Constants.ENTITY) && null != (param = paramMap.get(Constants.ENTITY))) {
    
    
            if (needToDecrypt(param.getClass())) {
    
    
                encryptEntity(param);
            }
            return;
        }
        // 通过在mapper.xml中自定义API修改数据库时
        if (paramMap.containsKey("entity") && null != (param = paramMap.get("entity"))) {
    
    
            if (needToDecrypt(param.getClass())) {
    
    
                encryptEntity(param);
            }
            return;
        }
        // 通过UpdateWrapper、LambdaUpdateWrapper修改数据库时
        if (paramMap.containsKey(Constants.WRAPPER) && null != (param = paramMap.get(Constants.WRAPPER))) {
    
    
            if (param instanceof Update && param instanceof AbstractWrapper) {
    
    
                Class<?> entityClass = mappedStatement.getParameterMap().getType();
                if (needToDecrypt(entityClass)) {
    
    
                    encryptWrapper(entityClass, param);
                }
            }
            return;
        }
    }

    /**
     * 校验该实例的类是否被@EncryptedTable所注解
     */
    private boolean needToDecrypt(Class<?> objectClass) {
    
    
        try {
    
    
            EncryptedTable sensitiveData = AnnotationUtils.findAnnotation(objectClass, EncryptedTable.class);
            return Objects.nonNull(sensitiveData);
        }catch (Exception ex){
    
    
            return  false;
        }

    }

    /**
     * 通过API(save、updateById等)修改数据库时
     *
     * @param parameter
     */
    private void encryptEntity(Object parameter) {
    
    
        //取出parameterType的类
        Class<?> resultClass = parameter.getClass();

        Field[] declaredFields =  ReflectUtil.getFields(resultClass);
        for (Field field : declaredFields) {
    
    
            //取出所有被EncryptedColumn注解的字段
            EncryptedColumn sensitiveField = field.getAnnotation(EncryptedColumn.class);
            if (!Objects.isNull(sensitiveField)) {
    
    
                field.setAccessible(true);
                Object object = null;
                try {
    
    
                    object = field.get(parameter);
                } catch (IllegalAccessException e) {
    
    
                    continue;
                }
                //只支持String的解密
                if (object instanceof String) {
    
    
                    String value = (String) object;
                    //对注解的字段进行逐一加密
                    try {
    
    
                        field.set(parameter, DBAesUtils.Encrypt(value));
                    } catch (IllegalAccessException e) {
    
    
                        continue;
                    }
                }
            }
        }
    }

    /**
     * 通过UpdateWrapper、LambdaUpdateWrapper修改数据库时
     *
     * @param entityClass
     * @param ewParam
     */
    private void encryptWrapper(Class<?> entityClass, Object ewParam) {
    
    
        AbstractWrapper updateWrapper = (AbstractWrapper) ewParam;
        String sqlSet = updateWrapper.getSqlSet();
        String[] elArr = sqlSet.split(",");
        Map<String, String> propMap = new HashMap<>(elArr.length);
        Arrays.stream(elArr).forEach(el -> {
    
    
            String[] elPart = el.split("=");
            propMap.put(elPart[0], elPart[1]);
        });

        //取出parameterType的类
        Field[] declaredFields = ReflectUtil.getFields(entityClass);
        for (Field field : declaredFields) {
    
    
            //取出所有被EncryptedColumn注解的字段
            EncryptedColumn sensitiveField = field.getAnnotation(EncryptedColumn.class);
            if (Objects.isNull(sensitiveField)) {
    
    
                continue;
            }
            String el = propMap.get(field.getName());
            Matcher matcher = PARAM_PAIRS_RE.matcher(el);
            if (matcher.matches()) {
    
    
                String valueKey = matcher.group(1);
                Object value = updateWrapper.getParamNameValuePairs().get(valueKey);
                updateWrapper.getParamNameValuePairs().put(valueKey, DBAesUtils.Encrypt(value.toString()));
            }
        }
    }
  }

(3)Interceptor傍受と復号化の実装


import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.plugin.*;
import org.springframework.core.annotation.AnnotationUtils;
import java.lang.reflect.Field;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Objects;

@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();
        if (Objects.isNull(resultObject)) {
    
    
            return null;
        }
        if(resultObject instanceof IPage){
    
    
            IPage page = (IPage) resultObject;
            if(page!=null&& CollUtil.isNotEmpty(page.getRecords())){
    
    
                if (needToDecrypt(page.getRecords().get(0))) {
    
    
                    for (Object result : page.getRecords()) {
    
    
                        //逐一解密
                        decrypt(result);
                    }
                }
            }
        }
        else if (resultObject instanceof ArrayList) {
    
    
            //基于selectList
            ArrayList resultList = (ArrayList) resultObject;
            if (CollUtil.isNotEmpty(resultList) && needToDecrypt(resultList.get(0))) {
    
    
                for (Object result : resultList) {
    
    
                    //逐一解密
                    decrypt(result);
                }
            }
        } else if (needToDecrypt(resultObject)) {
    
    
            //基于selectOne
            decrypt(resultObject);
        }
        return resultObject;
    }

    /**
     * 校验该实例的类是否被@EncryptedTable所注解
     */
    private boolean needToDecrypt(Object object) {
    
    
        Class<?> objectClass = object.getClass();
        EncryptedTable sensitiveData = AnnotationUtils.findAnnotation(objectClass, EncryptedTable.class);
        return Objects.nonNull(sensitiveData);
    }

    @Override
    public Object plugin(Object o) {
    
    
        if(o instanceof ResultSetHandler) {
    
    
          return   Plugin.wrap(o, this);
        }else{
    
    
            return o;
        }
    }

    private <T> T decrypt(T result) throws Exception {
    
    
        //取出resultType的类
        Class<?> resultClass = result.getClass();
        Field[] declaredFields = ReflectUtil.getFields(resultClass);
        for (Field field : declaredFields) {
    
    
            //取出所有被EncryptedColumn注解的字段
            EncryptedColumn sensitiveField = field.getAnnotation(EncryptedColumn.class);
            if (!Objects.isNull(sensitiveField)) {
    
    
                field.setAccessible(true);
                Object object = field.get(result);
                //只支持String的解密
                if (object instanceof String) {
    
    
                    String value = (String) object;
                    //对注解的字段进行逐一解密
                    String results =  DBAesUtils.Decrypt(value);
                    if(StrUtil.isNotEmpty(results)){
    
    
                        field.set(result, results);
                    }else{
    
    
                        field.set(result, value);
                    }

                }
            }
        }
        return result;
    }
}

注: 別のスターターとしてパッケージ化されている場合、このインターセプターspring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    com.xxx.xxx.xxx.xxx.DecryptInterceptor

(4) エンティティクラスにアノテーションを追加します(必須@EncryptedTableおよび@EncryptedColumn併用)

@Data
@TableName(value = "xxx",autoResultMap = true)
@EncryptedTable
public class xxx implements Serializable {
    
    

    private static final long serialVersionUID = 1L;

    @TableId(type = IdType.NONE, value = "id")
    private String id;

    @ApiModelProperty(value = "用户名")
    @TableField(value = "username")
    @EncryptedColumn
    private String username;

    @ApiModelProperty(value = "手机号")
    @TableField(value = "phone")
    @EncryptedColumn
    private String phone;
}

実装結果は最初の方法と同じです

おすすめ

転載: blog.csdn.net/m0_49605579/article/details/132854741