发布时间:2023-02-13 文章分类:编程知识 投稿人:王小丽 字号: 默认 | | 超大 打印

实现:

  1.   自定义注解类
  2.   自定义myabtis拦截器,拦截mybatis,主要涉及三个handler(StatementHandler,ParameterHandler,ResultSetHandler)
  3.   自定义加解密工具类
  4. 自定义业务处理Service(根据业务自行开发)
  5.   自定义注解添加再实体类及需要加解密字段上进行简单增改查测试
import java.lang.annotation.*;
/**
 * =====================================
 * *******开发部
 * =====================================
 *
 * @author 开发者
 * @version 1.0-SNAPSHOT
 *
 * @date 2023/2/6
 */
@Target({ElementType.TYPE, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface SensitiveEntity {
}



import java.lang.annotation.*;

/**
* =====================================
* ***********开发部
* =====================================
*
* @author 开发者
* @version 1.0-SNAPSHOT
*
* @date 2023/2/6
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface SensitiveField {
}
import ********.SensitiveEntity;
import ********.AesService;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
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.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Objects;
import java.util.Properties;
/**
 * =====================================
 * ***********开发部 
 * =====================================
 *
 * @version 1.0-SNAPSHOT
 * @description
 * @date 2023/2/10
 */
@Component
@Intercepts({
        @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}),
        @Signature(type = ParameterHandler.class, method = "setParameters", args = PreparedStatement.class),
        @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})
})
@Slf4j
public class MyBatisInterceptor implements Interceptor {
    @Resource
    private AesService aesService;
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object target = invocation.getTarget();
        //拦截sql结果处理器
        if (target instanceof ResultSetHandler) {
            return resultDecrypt(invocation);
        }
        //拦截sql参数处理器
        if (target instanceof ParameterHandler) {
            return parameterEncrypt(invocation);
        }
        //拦截sql语句处理器
        if (target instanceof StatementHandler) {
            return replaceSql(invocation);
        }
        return invocation.proceed();
    }
    /**
     * 对mybatis映射结果进行字段解密
     *
     * @param invocation 参数
     * @return 结果
     * @throws Throwable 异常
     */
    private Object resultDecrypt(Invocation invocation) throws Throwable {
        //取出查询的结果
        Object resultObject = invocation.proceed();
        if (Objects.isNull(resultObject)) {
            return null;
        }
        //基于selectList
        if (resultObject instanceof ArrayList) {
            ArrayList resultList = (ArrayList) resultObject;
            if (CollectionUtils.isEmpty(resultList) || !needToDecrypt(resultList.get(0))) {
                return resultObject;
            }
            for (Object result : resultList) {
                //逐一解密
                aesService.decrypt(result);
            }
            //基于selectOne
        } else {
            if (needToDecrypt(resultObject)) {
                aesService.decrypt(resultObject);
            }
        }
        return resultObject;
    }
    /**
     * mybatis映射参数进行加密
     *
     * @param invocation 参数
     * @return 结果
     * @throws Throwable 异常
     */
    private Object parameterEncrypt(Invocation invocation) throws Throwable {
        //@Signature 指定了 type= parameterHandler 后,这里的 invocation.getTarget() 便是parameterHandler
        //若指定ResultSetHandler ,这里则能强转为ResultSetHandler
        ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();
        // 获取参数对像,即 mapper 中 paramsType 的实例
        Field parameterField = parameterHandler.getClass().getDeclaredField("parameterObject");
        parameterField.setAccessible(true);
        //取出实例
        Object parameterObject = parameterField.get(parameterHandler);
        if (null == parameterObject) {
            return invocation.proceed();
        }
        Class<?> parameterObjectClass = parameterObject.getClass();
        //校验该实例的类是否被@SensitiveEntity所注解
        SensitiveEntity sensitiveEntity = AnnotationUtils.findAnnotation(parameterObjectClass, SensitiveEntity.class);
        //未被@SensitiveEntity所注解 则为null
        if (Objects.isNull(sensitiveEntity)) {
            return invocation.proceed();
        }
        //取出当前当前类所有字段,传入加密方法
        Field[] declaredFields = parameterObjectClass.getDeclaredFields();
        aesService.encrypt(declaredFields, parameterObject);
        return invocation.proceed();
    }
    /**
     * 替换mybatis Sql中的加密Key
     *
     * @param invocation 参数
     * @return 结果
     * @throws Throwable 异常
     */
    private Object replaceSql(Invocation invocation) throws Throwable {
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        BoundSql boundSql = statementHandler.getBoundSql();
        //获取到原始sql语句
        String sql = boundSql.getSql();
        sql = aesService.replaceAll(sql);
        if (null == sql){
            return invocation.proceed();
        }
        //通过反射修改sql语句
        Field field = boundSql.getClass().getDeclaredField("sql");
        field.setAccessible(true);
        field.set(boundSql, sql);
        return invocation.proceed();
    }
    /**
     * 判断是否包含需要加解密对象
     *
     * @param object 参数
     * @return 结果
     */
    private boolean needToDecrypt(Object object) {
        Class<?> objectClass = object.getClass();
        SensitiveEntity sensitiveEntity = AnnotationUtils.findAnnotation(objectClass, SensitiveEntity.class);
        return Objects.nonNull(sensitiveEntity);
    }
    @Override
    public Object plugin(Object target) {
        return Interceptor.super.plugin(target);
    }
    @Override
    public void setProperties(Properties properties) {
        Interceptor.super.setProperties(properties);
    }
}
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.Base64Utils;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
 * =====================================
 * ****************开发部
 * =====================================
 *
 * @author 开发者
 * @version 1.0-SNAPSHOT
 * @description 
 * @date 2023/2/10
 */
@Component
@Slf4j
public class AesFieldUtils {
    /**
     * 加密算法
     */
    private final String KEY_ALGORITHM = "AES";
    /**
     * 算法/模式/补码方式
     */
    private final String DEFAULT_CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding";
    /**
     * 编码格式
     */
    private final String CODE = "utf-8";
    /**
     * base64验证规则
     */
    private final String BASE64_RULE = "^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{4}|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)$";
    /**
     * 正则验证对象
     */
    private final Pattern PATTERN = Pattern.compile(BASE64_RULE);
    /**
     * 加解密 密钥key
     */
    @Value("${encrypt.key}")
    public static String encryptKey;
    /**
     * @param content 加密字符串
     * @return 加密结果
     */
    public String encrypt(String content) {
        return encrypt(content, encryptKey);
    }
    /**
     * 加密
     *
     * @param content 加密参数
     * @param key     加密key
     * @return 结果字符串
     */
    public String encrypt(String content, String key) {
        //判断如果已经是base64加密字符串则返回原字符串
        if (isBase64(content)) {
            return content;
        }
        byte[] encrypted = encrypt2bytes(content, key);
        if (null == encrypted || encrypted.length < 1) {
            log.info("加密字符串[{}]转字节为null", content);
            return null;
        }
        return Base64Utils.encodeToString(encrypted);
    }
    /**
     * @param content 加密字符串
     * @param key     加密key
     * @return 返回加密字节
     */
    public byte[] encrypt2bytes(String content, String key) {
        try {
            byte[] raw = key.getBytes(CODE);
            SecretKeySpec secretKeySpec = new SecretKeySpec(raw, KEY_ALGORITHM);
            Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
            return cipher.doFinal(content.getBytes(CODE));
        } catch (Exception e) {
            log.error("failed to encrypt: {} of {}", content, e);
            return null;
        }
    }
    /**
     * @param content 加密字符串
     * @return 返回加密结果
     */
    public String decrypt(String content) {
        try {
            return decrypt(content, encryptKey);
        } catch (Exception e) {
            log.error("failed to decrypt: {}, e: {}", content, e);
            return null;
        }
    }
    /**
     * 解密
     *
     * @param content 解密字符串
     * @param key     解密key
     * @return 解密结果
     */
    public String decrypt(String content, String key) throws Exception {
        //不是base64格式字符串则不进行解密
        if (!isBase64(content)) {
            return content;
        }
        return decrypt(Base64Utils.decodeFromString(content), key);
    }
    /**
     * @param content 解密字节
     * @param key     解密key
     * @return 返回解密内容
     */
    public String decrypt(byte[] content, String key) throws Exception {
        if (key == null) {
            log.error("AES key should not be null");
            return null;
        }
        byte[] raw = key.getBytes(CODE);
        SecretKeySpec keySpec = new SecretKeySpec(raw, KEY_ALGORITHM);
        Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, keySpec);
        try {
            byte[] original = cipher.doFinal(content);
            return new String(original, CODE);
        } catch (Exception e) {
            log.error("failed to decrypt content: {}/ key: {}, e: {}", content, key, e);
            return null;
        }
    }
    /**
     * 判断是否为 base64加密
     *
     * @param str 参数
     * @return 结果
     */
    private boolean isBase64(String str) {
        Matcher matcher = PATTERN.matcher(str);
        return matcher.matches();
    }
}
import *****************.SensitiveField;
import *****************.AesService;
import *****************.AesFieldUtils;
import *****************.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.util.Objects;
/**
 * =====================================
 * *****************开发部
 * =====================================
 *
 * @author 开发者
 * @version 1.0-SNAPSHOT
 * @description 
 * @date 2023/2/10
 */
@Service
public class AesServiceImpl implements AesService {
    @Value("${aes.key}")
    private String key;
    @Value("${aes.keyField}")
    private String keyField;
    @Resource
    private AesFieldUtils aesFieldUtils;
    @Override
    public <T> T encrypt(Field[] declaredFields, T paramsObject) throws Exception {
        for (Field field : declaredFields) {
            //取出所有被EncryptDecryptField注解的字段
            SensitiveField sensitiveField = field.getAnnotation(SensitiveField.class);
            if (Objects.isNull(sensitiveField)) {
                continue;
            }
            field.setAccessible(true);
            Object object = field.get(paramsObject);
            //暂时只实现String类型的加密
            if (object instanceof String) {
                String value = (String) object;
                //如果映射字段值为空,并且以==结尾则跳过不进行加密
                if (!StringUtils.noEmpty(value)) {
                    continue;
                }
                //加密  这里我使用自定义的AES加密工具
                field.set(paramsObject, aesFieldUtils.encrypt(value, key));
            }
        }
        return paramsObject;
    }
    @Override
    public <T> T decrypt(T result) throws Exception {
        //取出resultType的类
        Class<?> resultClass = result.getClass();
        Field[] declaredFields = resultClass.getDeclaredFields();
        for (Field field : declaredFields) {
            //取出所有被EncryptDecryptField注解的字段
            SensitiveField sensitiveField = field.getAnnotation(SensitiveField.class);
            if (Objects.isNull(sensitiveField)) {
                continue;
            }
            field.setAccessible(true);
            Object object = field.get(result);
            //只支持String的解密
            if (object instanceof String) {
                String value = (String) object;
                //如果映射字段值为空,并且不已==结尾则跳过不进行解密
                if (!StringUtils.noEmpty(value)) {
                    continue;
                }
                //对注解的字段进行逐一解密
                field.set(result, aesFieldUtils.decrypt(value, key));
            }
        }
        return result;
    }
    @Override
    public String replaceAll(String sql) {
        if (sql.contains(keyField)) {
            return sql.replaceAll(keyField, key);
        }
        return null;
    }
}
import **************.SensitiveEntity;
import **************.SensitiveField;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.activerecord.Model;
@SensitiveEntity
@TableName("t_users")
public class Users extends Model<Users> {
    /**
     *
     * This field was generated by MyBatis Generator.
     * This field corresponds to the database column t_users.id
     *
     * @mbg.generated Wed Feb 01 10:03:44 CST 2023
     */
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;
    /**
     *
     * This field was generated by MyBatis Generator.
     * This field corresponds to the database column t_users.user_id
     *
     * @mbg.generated Wed Feb 01 10:03:44 CST 2023
     */
    private String userId;
    /**
     *
     * This field was generated by MyBatis Generator.
     * This field corresponds to the database column t_users.user_name
     *
     * @mbg.generated Wed Feb 01 10:03:44 CST 2023
     */
    private String userName;
    /**
     *
     * This field was generated by MyBatis Generator.
     * This field corresponds to the database column t_users.nick_name
     *
     * @mbg.generated Wed Feb 01 10:03:44 CST 2023
     */
    private String nickName;
    /**
     *
     * This field was generated by MyBatis Generator.
     * This field corresponds to the database column t_users.password
     *
     * @mbg.generated Wed Feb 01 10:03:44 CST 2023
     */
    @SensitiveField
    private String password;
    /**
     *
     * This field was generated by MyBatis Generator.
     * This field corresponds to the database column t_users.pwd_duration
     *
     * @mbg.generated Wed Feb 01 10:03:44 CST 2023
     */
    private String pwdDuration;
    @SensitiveField
    private String birth;
}

@Test
public void add(){
Users user = new Users();
user.setUserId("test03");
user.setUserName("小明004");
user.setNickName("test03");
user.setPassword("12343454123");
user.setPwdDuration("124124124214");

usersService.insert(user);
}

@Test
public void update(){
Users user = new Users();
user.setUserId("test03");
user.setUserName("小明03");
user.setNickName("test03");
user.setPassword("1252525125123");
user.setPwdDuration("1252525125121");
usersService.updateByPrimaryKeySelective(user);
}

@Test
public void queryTest(){
System.out.println(JSON.toJSON(usersService.selectByPrimaryKey(3)));
}

本文原创:如有使用请标明出处