拦截器加密字段

package com.zhongke.common.security.annotation;

import java.lang.annotation.*;

/**
 * <p>作用于类:标识当前实体需要进行结果解密操作.
 * <p>作用于字段:标识当前实体的字段需要进行加解密操作.
 * <p>作用于方法:标识当前mapper方法会被切面进行拦截,并进行数据的加解密操作.
 * <p>注意:如果作用于字段,那当前类必须先标注该注解,因为会优先判断类是否需要加解密,然后在判断字段是否需要加解密,否则只作用于字段不会起效
 *
 * @author zrh
 * @date 2022/1/4
 */
@Documented
@Inherited
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Crypt {
    /**
     * 默认字段需要解密
     */
    boolean decrypt () default true;
    /**
     * 默认字段需要加密
     */
    boolean encrypt () default true;
    /**
     * 字段为对象时有用,默认当前对象不需要进行加解密
     */
    boolean subObject () default false;
    /**
     * 需要进行加密的字段列下标
     */
    int[] encryptParamIndex () default {};
}
package com.zhongke.common.security.handler;

import com.zhongke.common.security.annotation.Crypt;
import com.zhongke.common.security.utils.AesTools;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * @author zrh
 * @date 2022/1/2
 */
@Slf4j
@Component
public class MybatisCryptHandler {

    private final static ThreadLocal<List> THREAD_LOCAL = ThreadLocal.withInitial(() -> new ArrayList());
    private static final List<Field> EMPTY_FIELD_ARRAY = new ArrayList();
    /**
     * Cache for {@link Class#getDeclaredFields()}, allowing for fast iteration.
     */
    private static final Map<Class<?>, List<Field>> declaredFieldsCache = new ConcurrentHashMap<>(256);

    /**
     * 参数对外加密方法
     * @param handler
     */
    public void parameterEncrypt (ParameterHandler handler) {
        Object parameterObject = handler.getParameterObject();
        if (null == parameterObject || parameterObject instanceof String) {
            return;
        }
        encryptFieldHandler(parameterObject);
        removeLocal();
    }

    /**
     * 参数加密规则方法
     * @param sourceObject
     */
    private void encryptFieldHandler (Object sourceObject) {
        if (null == sourceObject) {
            return;
        }
        if (sourceObject instanceof Map) {
            ((Map<?, Object>) sourceObject).values().forEach(this::encryptFieldHandler);
            return;
        }
        if (sourceObject instanceof List) {
            ((List<?>) sourceObject).stream().forEach(this::encryptFieldHandler);
            return;
        }
        Class<?> clazz = sourceObject.getClass();
        if (!clazz.isAnnotationPresent(Crypt.class)) {
            return;
        }
        if (checkLocal(sourceObject)) {
            return;
        }
        setLocal(sourceObject);
        try {
            Field[] declaredFields = clazz.getDeclaredFields();
            // 获取满足加密注解条件的字段
            final List<Field> collect = Arrays.stream(declaredFields).filter(this::checkEncrypt).collect(Collectors.toList());
            for (Field item : collect) {
                item.setAccessible(true);
                Object value = item.get(sourceObject);
                if (null != value && value instanceof String) {
                    item.set(sourceObject, AesTools.encryptECB((String) value));
                }
            }
        } catch (Exception e) {
        }
    }

    /**
     * 解析注解 - 加密密方法
     * @param field
     * @return
     */
    private boolean checkEncrypt (Field field) {
        Crypt crypt = field.getAnnotation(Crypt.class);
        return null != crypt && crypt.encrypt();
    }

    /**
     * 查询结果对外解密方法
     * @param resultData
     */
    public Object resultDecrypt (Object resultData) {
        if (resultData instanceof List) {
            return ((List<?>) resultData).stream().map(this::resultObjHandler).collect(Collectors.toList());
        }
        return resultObjHandler(resultData);
    }

    /**
     * 查询结果解密规则方法
     * @param result
     */
    private Object resultObjHandler (Object result) {
        if (null == result) {
            return null;
        }
        Class<?> clazz = result.getClass();
        //获取所有要解密的字段
        Field[] declaredFields = getAllFieldsCache(clazz);
        Arrays.stream(declaredFields).forEach(item -> {
            try {
                item.setAccessible(true);
                Object value = item.get(result);
                if (null != value && value instanceof String) {
                    item.set(result, AesTools.decryptECB((String) value));
                }
            } catch (Exception e) {
                log.error("DecryptException -> checkDecrypt:", e);
            }
        });

        Arrays.stream(declaredFields).filter(item -> checkSubObject(item)).forEach(item -> {
            item.setAccessible(true);
            try {
                Object data = item.get(result);
                if (data instanceof List) {
                    ((List<?>) data).forEach(this::resultObjHandler);
                }
            } catch (IllegalAccessException e) {
                log.error("DecryptException -> checkSubObject:{}", e);
            }
        });
        return result;
    }

    /**
     * 解析注解 - 解密方法
     * @param field
     * @return
     */
    private static boolean checkDecrypt (Field field) {
        Crypt crypt = field.getAnnotation(Crypt.class);
        return null != crypt && crypt.decrypt();
    }

    /**
     * 解析注解 - 子对象
     * @param field
     * @return
     */
    private static boolean checkSubObject (Field field) {
        Crypt crypt = field.getAnnotation(Crypt.class);
        return null != crypt && crypt.subObject();
    }

    /**
     * 对请求参数进行解密还原,
     * @param requestObject
     */
    public void decryptFieldHandler (Object requestObject) {
        if (null == requestObject) {
            return;
        }
        if (requestObject instanceof Map) {
            ((Map<?, Object>) requestObject).values().forEach(this::decryptFieldHandler);
            return;
        }
        if (requestObject instanceof List) {
            ((List<?>) requestObject).stream().forEach(this::decryptFieldHandler);
            return;
        }
        Class<?> clazz = requestObject.getClass();
        if (!clazz.isAnnotationPresent(Crypt.class)) {
            return;
        }
        try {
            Field[] declaredFields = clazz.getDeclaredFields();
            // 获取满足加密注解条件的字段
            final List<Field> collect = Arrays.stream(declaredFields).filter(this::checkEncrypt).collect(Collectors.toList());
            for (Field item : collect) {
                item.setAccessible(true);
                Object value = item.get(requestObject);
                if (null != value && value instanceof String) {
                    item.set(requestObject, AesTools.decryptECB((String) value));
                }
            }
        } catch (Exception e) {
        }
    }

    /**
     * 统一管理内存
     * @param o
     * @return
     */
    private boolean checkLocal (Object o) {
        return THREAD_LOCAL.get().contains(o);
    }
    private void setLocal (Object o) {
        THREAD_LOCAL.get().add(o);
    }
    private void removeLocal () {
        THREAD_LOCAL.get().clear();
    }

    /**
     * 获取本类及其父类的属性的方法
     * @param clazz 当前类对象
     * @return 字段数组
     */
    private static Field[] getAllFields (Class<?> clazz) {
        List<Field> fieldList = new ArrayList<>();
        while (clazz != null) {
            fieldList.addAll(new ArrayList<>(Arrays.asList(clazz.getDeclaredFields())));
            clazz = clazz.getSuperclass();
        }
        Field[] fields = new Field[fieldList.size()];
        return fieldList.toArray(fields);
    }

    /**
     * 获取本类及其父类的属性的方法
     * @param clazz 当前类对象
     * @return 字段数组
     */
    private static Field[] getAllFieldsCache (Class<?> clazz) {
        List<Field> fieldList = new ArrayList<>();
        while (clazz != null) {
            if (clazz.isAnnotationPresent(Crypt.class)) {
                fieldList.addAll(getDeclaredFields(clazz));
            }
            clazz = clazz.getSuperclass();
        }
        Field[] fields = new Field[fieldList.size()];
        return fieldList.toArray(fields);
    }

    private static List<Field> getDeclaredFields (Class<?> clazz) {
        List<Field> result = declaredFieldsCache.get(clazz);
        if (result == null) {
            try {
                // 获取满足注解解密条件的字段
                result = Arrays.stream(clazz.getDeclaredFields()).filter(MybatisCryptHandler::checkDecrypt).collect(Collectors.toList());
                // 放入本地缓存
                declaredFieldsCache.put(clazz, (result.isEmpty() ? EMPTY_FIELD_ARRAY : result));
            } catch (Exception e) {
                log.error("getDeclaredFields:", e);
            }
        }
        return result;
    }
}
package com.zhongke.common.security.interceptor;

import com.zhongke.common.security.handler.MybatisCryptHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.sql.Statement;
import java.util.Arrays;
import java.util.Properties;

/**
 * 对mybatis查询结果进行拦截解密,并对请求参数进行拦截解密还原操作
 * @author zrh
 */
@Slf4j
@Component
@Intercepts({
        @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class}),
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
})
public class MybatisDecryptInterceptor implements Interceptor {

    @Resource
    private MybatisCryptHandler handler;

    @Override
    public Object intercept (Invocation invocation) throws Exception {
        // 获取执行mysql执行结果
        Object result = invocation.proceed();
        if (invocation.getTarget() instanceof Executor) {
            // 对增删改操作方法的请求参数进行解密还原操作
            checkEncryptByUpdate(invocation.getArgs());
            return result;
        }
        // 对查询方法的请求参数进行解密还原操作
        checkEncryptByQuery(invocation.getTarget());
        // 对查询结果进行解密
        return handler.resultDecrypt(result);
    }

    @Override
    public Object plugin (Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties (Properties properties) {
    }

    /**
     * 对请求参数进行解密还原操作
     * @param target
     */
    private void checkEncryptByQuery (Object target) {
        try {
            final Class<?> targetClass = target.getClass();
            final Field parameterHandlerFiled = targetClass.getDeclaredField("parameterHandler");
            parameterHandlerFiled.setAccessible(true);
            final Object parameterHandler = parameterHandlerFiled.get(target);
            final Class<?> parameterHandlerClass = parameterHandler.getClass();
            final Field parameterObjectField = parameterHandlerClass.getDeclaredField("parameterObject");
            parameterObjectField.setAccessible(true);
            final Object parameterObject = parameterObjectField.get(parameterHandler);
            handler.decryptFieldHandler(parameterObject);
        } catch (Exception e) {
            log.error("对请求参数进行解密还原操作异常:", e);
        }
    }

    /**
     * 对请求参数进行解密还原操作
     * @param args
     */
    private void checkEncryptByUpdate (Object[] args) {
        try {
            Arrays.stream(args).forEach(handler::decryptFieldHandler);
        } catch (Exception e) {
            log.error("对请求参数进行解密还原操作异常:", e);
        }
    }
}
package com.zhongke.common.security.interceptor;

import com.zhongke.common.security.handler.MybatisCryptHandler;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.parameter.ParameterHandler;
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.stereotype.Component;

import javax.annotation.Resource;
import java.sql.PreparedStatement;
import java.util.Properties;

/**
 * 对mybatis入参进行拦截加密
 * @author zrh
 */
@Slf4j
@Component
@Intercepts(@Signature(type = ParameterHandler.class, method = "setParameters", args = {PreparedStatement.class}))
public class MybatisEncryptInterceptor implements Interceptor {

    @Resource
    private MybatisCryptHandler handler;

    @Override
    public Object intercept (Invocation invocation) {
        return invocation;
    }

    @SneakyThrows
    @Override
    public Object plugin (Object target) {
        if (target instanceof ParameterHandler) {
            // 对请求参数进行加密操作
            handler.parameterEncrypt((ParameterHandler) target);
        }
        return target;
    }

    @Override
    public void setProperties (Properties properties) {
    }
}
package com.zhongke.common.security.utils;

import com.zhongke.common.core.utils.StringUtils;
import lombok.extern.slf4j.Slf4j;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;

/**
 * AES加密工具
 *
 * @author zrh
 * @date 2022/1/3
 */
@Slf4j
public class AesTools {
    private AesTools () {
    }

    private static final String KEY_ALGORITHM = "AES";
    private static final String ENCODING = "UTF-8";
    private static final String DEFAULT_CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding";
    private static Cipher ENCODING_CIPHER = null;
    private static Cipher DECRYPT_CIPHER = null;
    /**
     * 秘钥
     */
    private static final String KEY = "abcdabcdZKRBZKRB";

    static {
        try {
            // 初始化cipher
            ENCODING_CIPHER = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);
            DECRYPT_CIPHER = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);
            //转化成JAVA的密钥格式
            SecretKeySpec keySpec = new SecretKeySpec(KEY.getBytes("ASCII"), KEY_ALGORITHM);
            ENCODING_CIPHER.init(Cipher.ENCRYPT_MODE, keySpec);
            DECRYPT_CIPHER.init(Cipher.DECRYPT_MODE, keySpec);
        } catch (Exception e) {
            log.error("初始化mybatis -> AES加解密参数异常:", e);
        }
    }

    /**
     * AES加密
     * @param content 加密内容
     * @return
     */
    public static String encryptECB (String content) {
        if (StringUtils.isEmpty(content)) {
            return content;
        }
        String encryptStr = content;
        try {
            byte[] encrypted = ENCODING_CIPHER.doFinal(content.getBytes(ENCODING));
            encryptStr = Base64.getEncoder().encodeToString(encrypted);
        } catch (Exception e) {
            e.printStackTrace();
            log.info("mybatis -> AES加密出错:{}", content);
        }
        return encryptStr;
    }

    /**
     * AES解密
     * @param content 解密内容
     * @return
     */
    public static String decryptECB (String content) {
        if (StringUtils.isEmpty(content)) {
            return content;
        }
        String decryptStr = content;
        try {
            byte[] decrypt = DECRYPT_CIPHER.doFinal(Base64.getDecoder().decode(content));
            decryptStr = new String(decrypt, ENCODING);
        } catch (Exception e) {
            log.info("mybatis -> AES解密出错:{}", content);
        }
        return decryptStr;
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值