程老の
活到老,学到老
随笔 - 8,  文章 - 0,  评论 - 0,  阅读 - 6623

Mybatis拦截器对数据库敏感字段加解密

一、前言

     公司业务需求,需要对客户敏感字段进行加密处理,其实挺头疼的,因为有很多数据要处理,第一版我们做的做法,就是在dao层上写一层代理类,把所有的dao层的接口全部实现一遍处理加解密,service引入写的代理类,这样处理其实很麻烦,代码维护方面以及可读性都很差,于是百度搜了很多方式想通过一些方式能不能统一处理,网上看了很久很多没有符合我的,要么就是写了一半,只能看他的方向,搜了很多篇文章,找到一个还不错,自己拿来完善用了起来.

二、思想

    mybatis拦截Executor.class对象中的query,update方法,拦截将参数重新处理,这里对实体类,单个参数,以及多个参数处理,map(未实现)通过注解的方式动态对字段的值进行加解密,将查询的结果进行解密拦截返回.

三、本项目的Springboot项目,具体实现如下

     1.使用的加密是Aes加密(加密方式多多,自己选择)

复制代码

import com.*.bean.enums.AesEnum;
import org.thymeleaf.util.StringUtils;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;


/**
* @Description 修改工具类
* 新增前端交互加密解密AES工具类(RandomStringUtils 随机生成key和偏移量)
*/
public class AESUtil {
private static final String AES_KEY = "jafndsafa";
private static final String AES_IV = "411231411313";


public static String encrypt(String value, AesEnum aesEnum) {
try {
return assemble(value, aesEnum.getKey(), aesEnum.getIv());
} catch (Exception e) {
return null;
}
}


public static String encrypt(String value) {
try {
return assemble(value, AES_KEY, AES_IV);
} catch (Exception e) {
return null;
}
}


public static String decrypt(String secValue, AesEnum aesEnum) {
if (StringUtils.isEmpty(secValue)) return null;
try {
return disassemble(secValue, aesEnum.getKey(), aesEnum.getIv());
} catch (Exception e) {
return null;
}
}


public static String decrypt(String secValue) {
if (StringUtils.isEmpty(secValue)) return null;
try {
return disassemble(secValue, AES_KEY, AES_IV);
} catch (Exception e) {
return null;
}
}


public static String encrypt(String value, String key, String iv) {
try {
return assemble(value, key, iv);
} catch (Exception e) {
return null;
}
}


public static String decrypt(String secValue, String key, String iv) {
try {
return disassemble(secValue, key, iv);
} catch (Exception e) {
return null;
}
}


private static String assemble(String sSrc, String sKey, String ivStr) throws Exception {
if (sKey == null) {
return null;
}
if (sKey.length() != 16) {
return null;
}
byte[] raw = sKey.getBytes();
SecretKeySpec sKeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
if (ivStr != null) {
IvParameterSpec iv = new IvParameterSpec(ivStr.getBytes());
cipher.init(Cipher.ENCRYPT_MODE, sKeySpec, iv);
} else {
cipher.init(Cipher.ENCRYPT_MODE, sKeySpec);
}
byte[] encrypted = cipher.doFinal(sSrc.getBytes());
return byte2hex(encrypted).toLowerCase();
}


private static String disassemble(String sSrc, String sKey, String ivStr) throws Exception {
try {
if (sKey == null) {
return null;
}
if (sKey.length() != 16) {
return null;
}
byte[] raw = sKey.getBytes("ASCII");
SecretKeySpec sKeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
if (ivStr != null) {
IvParameterSpec iv = new IvParameterSpec(ivStr.getBytes());
cipher.init(Cipher.DECRYPT_MODE, sKeySpec, iv);
} else {
cipher.init(Cipher.DECRYPT_MODE, sKeySpec);
}
byte[] encrypted1 = hex2byte(sSrc);
try {
byte[] original = cipher.doFinal(encrypted1);
String originalString = new String(original);
return originalString;
} catch (Exception e) {
return null;
}
} catch (Exception ex) {
return null;
}
}


public static byte[] hex2byte(String strhex) {
if (strhex == null) {
return null;
}
int l = strhex.length();
if (l % 2 == 1) {
return null;
}
byte[] b = new byte[l / 2];
for (int i = 0; i != l / 2; i++) {
b[i] = (byte) Integer.parseInt(strhex.substring(i * 2, i * 2 + 2), 16);
}
return b;
}


public static String byte2hex(byte[] b) {
String hs = "";
String stmp = "";
for (int n = 0; n < b.length; n++) {
stmp = (Integer.toHexString(b[n] & 0XFF));
if (stmp.length() == 1) {
hs = hs + "0" + stmp;
} else {
hs = hs + stmp;
}
}
return hs.toUpperCase();
}


}


复制代码

 

2.自定义注解

复制代码
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @decription DecryptField
 * <p>字段解密注解</p>
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DecryptField {
    String value() default "";
}
复制代码

 

     

复制代码
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @decription EncryptField
 * <p>字段加密注解</p>
 */
@Target({ElementType.FIELD,ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptField {
    String value() default "";
}
复制代码

 

复制代码
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.FIELD,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptMethod {
    String[] encrypt() default "";
    String[] decrypt() default "";
}
复制代码

3.自定义拦截代码如下:

复制代码
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import com.*.annotation.EncryptField;
import com.*.annotation.EncryptMethod;
import com.*.util.CryptPojoUtils;
import com.*.util.JsonUtil;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


@Intercepts({
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class DataInterceptor implements Interceptor {
    private final Logger logger = LoggerFactory.getLogger(DataInterceptor.class);
    static int MAPPED_STATEMENT_INDEX = 0;
    static int PARAMETER_INDEX = 1;
    static int ROWBOUNDS_INDEX = 2;
    static int RESULT_HANDLER_INDEX = 3;
    static String ENCRYPTFIELD = "1";
    static String DECRYPTFIELD = "2";
    private static boolean ENCRYPT_SWTICH = true;

    /**
     * 是否进行加密查询
     *
     * @return 1 true 代表加密 0 false 不加密
     */
    private boolean getFuncSwitch() {
        return ENCRYPT_SWTICH;
    }

    /**
     * 校验执行器方法 是否在白名单中
     *
     * @param statementid
     * @return true 包含 false 不包含
     */
    private boolean isWhiteList(String statementid) {
        boolean result = false;
//        String whiteStatementid = "com.*.dao.UserDao.save";
        String whiteStatementid = "";
        if (whiteStatementid.indexOf(statementid) != -1) {
            result = true;
        }
        return result;
    }

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        logger.info("EncryptDaoInterceptor.intercept开始执行==> ");
        MappedStatement statement = (MappedStatement) invocation.getArgs()[MAPPED_STATEMENT_INDEX];
        Object parameter = invocation.getArgs()[PARAMETER_INDEX];
        logger.info(statement.getId() + "未加密参数串:" + JsonUtil.DEFAULT.toJson(parameter));
        /*
         * 判断是否拦截白名单 或 加密开关是否配置,
         * 如果不在白名单中,并且本地加密开关 已打开 执行参数加密
         */
        if (!isWhiteList(statement.getId()) && getFuncSwitch()) {
            parameter = encryptParam(parameter, invocation);
            logger.info(statement.getId() + "加密后参数:" + JsonUtil.DEFAULT.toJson(parameter));
        }
        invocation.getArgs()[PARAMETER_INDEX] = parameter;
        Object returnValue = invocation.proceed();
        logger.info(statement.getId() + "未解密结果集:" + JsonUtil.DEFAULT.toJson(returnValue));
        returnValue = decryptReslut(returnValue, invocation);
        logger.info(statement.getId() + "解密后结果集:" + JsonUtil.DEFAULT.toJson(returnValue));
        logger.info("EncryptDaoInterceptor.intercept执行结束==> ");
        return returnValue;
    }

    /**
     * 解密结果集
     *
     * @param @param  returnValue
     * @param @param  invocation
     * @param @return
     * @return Object
     * @throws
     */
    public Object decryptReslut(Object returnValue, Invocation invocation) {
        MappedStatement statement = (MappedStatement) invocation.getArgs()[MAPPED_STATEMENT_INDEX];
        if (returnValue != null) {
            if (returnValue instanceof ArrayList<?>) {
                List<?> list = (ArrayList<?>) returnValue;
                List<Object> newList = new ArrayList<Object>();
                if (1 <= list.size()) {
                    for (Object object : list) {
                        Object obj = CryptPojoUtils.decrypt(object);
                        newList.add(obj);
                    }
                    returnValue = newList;
                }
            } else if (returnValue instanceof Map) {
                String[] fields = getEncryFieldList(statement, DECRYPTFIELD);
                if (fields != null) {
                    returnValue = CryptPojoUtils.getDecryptMapValue(returnValue, fields);
                }
            } else {
                returnValue = CryptPojoUtils.decrypt(returnValue);
            }
        }
        return returnValue;
    }

    /***
     * 针对不同的参数类型进行加密
     * @param @param parameter
     * @param @param invocation
     * @param @return
     * @return Object
     * @throws
     *
     */
    public Object encryptParam(Object parameter, Invocation invocation) {
        MappedStatement statement = (MappedStatement) invocation.getArgs()[MAPPED_STATEMENT_INDEX];
        try {
            if (parameter instanceof String) {
                if (isEncryptStr(statement)) {
                    parameter = CryptPojoUtils.encryptStr(parameter);
                }
            } else if (parameter instanceof Map) {
                String[] fields = getEncryFieldList(statement, ENCRYPTFIELD);
                if (fields != null) {
                    parameter = CryptPojoUtils.getEncryptMapValue(parameter, fields);
                }
            } else {
                parameter = CryptPojoUtils.encrypt(parameter);
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            logger.info("EncryptDaoInterceptor.encryptParam方法异常==> " + e.getMessage());
        }
        return parameter;
    }

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

    @Override
    public void setProperties(Properties properties) {

    }

    /**
     * 获取参数map中需要加密字段
     *
     * @param statement
     * @param type
     * @return List<String>
     * @throws
     */
    private String[] getEncryFieldList(MappedStatement statement, String type) {
        String[] strArry = null;
        Method method = getDaoTargetMethod(statement);
        Annotation annotation = method.getAnnotation(EncryptMethod.class);
        if (annotation != null) {
            if (type.equals(ENCRYPTFIELD)) {
                strArry = ((EncryptMethod) annotation).encrypt();
            } else if (type.equals(DECRYPTFIELD)) {
                strArry = ((EncryptMethod) annotation).decrypt();
            } else {
                strArry = null;
            }
        }
        return strArry;
    }

    /**
     * 获取Dao层接口方法
     *
     * @param @return
     * @return Method
     * @throws
     */
    private Method getDaoTargetMethod(MappedStatement mappedStatement) {
        Method method = null;
        try {
            String namespace = mappedStatement.getId();
            String className = namespace.substring(0, namespace.lastIndexOf("."));
            String methedName = namespace.substring(namespace.lastIndexOf(".") + 1, namespace.length());
            Method[] ms = Class.forName(className).getMethods();
            for (Method m : ms) {
                if (m.getName().equals(methedName)) {
                    method = m;
                    break;
                }
            }
        } catch (SecurityException e) {
            e.printStackTrace();
            logger.info("EncryptDaoInterceptor.getDaoTargetMethod方法异常==> " + e.getMessage());
            return method;

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            logger.info("EncryptDaoInterceptor.getDaoTargetMethod方法异常==> " + e.getMessage());
            return method;
        }
        return method;
    }

    /**
     * 判断字符串是否需要加密
     *
     * @param @param  mappedStatement
     * @param @return
     * @return boolean
     * @throws
     */
    private boolean isEncryptStr(MappedStatement mappedStatement) throws ClassNotFoundException {
        boolean reslut = false;
        try {
            Method m = getDaoTargetMethod(mappedStatement);
            m.setAccessible(true);
            Annotation[][] parameterAnnotations = m.getParameterAnnotations();
            if (parameterAnnotations != null && parameterAnnotations.length > 0) {
                for (Annotation[] parameterAnnotation : parameterAnnotations) {
                    for (Annotation annotation : parameterAnnotation) {
                        if (annotation instanceof EncryptField) {
                            reslut = true;
                        }
                    }
                }
            }
        } catch (SecurityException e) {
            e.printStackTrace();
            logger.info("EncryptDaoInterceptor.isEncryptStr异常:==> " + e.getMessage());
            reslut = false;
        }
        return reslut;
    }

}
复制代码

 

类中使用工具如下:

复制代码
import com.*.annotation.DecryptField;
import com.*.annotation.EncryptField;
import org.apache.commons.lang3.StringUtils;

import java.lang.reflect.Field;
import java.util.Map;

/**
 * @decription CryptPojoUtils
 * <p>对象加解密工具
 * 通过反射,对参数对象中包含指定注解的字段进行加解密。
 * 调用<tt>encrypt(T t)</tt>方法实现加密,返回加密后的对象;
 * 调用<tt>decrypt(T t)</tt>实现解密,返回解密后的对象;
 * <tt>encrypt</tt>对注解{@link EncryptField}字段有效;
 * <tt>decrypt</tt>对注解{@link DecryptField}字段有效。</p>
 */
public class CryptPojoUtils {
    /**
     * 对象t注解字段加密
     *
     * @param t
     * @param <T>
     * @return
     */
    public static <T> T encrypt(T t) {
        if (isEncryptAndDecrypt(t)) {
            Field[] declaredFields = t.getClass().getDeclaredFields();
            try {
                if (declaredFields != null && declaredFields.length > 0) {
                    for (Field field : declaredFields) {
                        if (field.isAnnotationPresent(EncryptField.class) && field.getType().toString().endsWith("String")) {
                            field.setAccessible(true);
                            String fieldValue = (String) field.get(t);
                            if (StringUtils.isNotEmpty(fieldValue)) {
                                field.set(t, AESUtil.encrypt(fieldValue));
                            }
                            field.setAccessible(false);
                        }
                    }
                }
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        }
        return t;
    }

    /**
     * 对含注解字段加密
     * @param t
     * @param <T>
     */
    public static <T> void encryptField(T t) {
        Map<String,Object> map = (Map<String, Object>) t;
        Field[] declaredFields = t.getClass().getDeclaredFields();
        try {
            if (declaredFields != null && declaredFields.length > 0) {
                for (Field field : declaredFields) {
                    if (field.isAnnotationPresent(EncryptField.class) && field.getType().toString().endsWith("String")) {
                        field.setAccessible(true);
                        String fieldValue = (String)field.get(t);
                        if(StringUtils.isNotEmpty(fieldValue)) {
                            field.set(t, AESUtil.encrypt(fieldValue));
                        }
                    }
                }
            }
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 加密单独的字符串
     *
     * @param @param  t
     * @param @return
     * @return T
     * @throws
     */
    public static <T> T encryptStr(T t) {
        if (t instanceof String) {
            t = (T) AESUtil.encrypt((String) t);
        }
        return t;
    }

    /**
     * 对含注解字段解密
     *
     * @param t
     * @param <T>
     */
    public static <T> T decrypt(T t) {
        if (isEncryptAndDecrypt(t)) {
            Field[] declaredFields = t.getClass().getDeclaredFields();
            try {
                if (declaredFields != null && declaredFields.length > 0) {
                    for (Field field : declaredFields) {
                        if (field.isAnnotationPresent(DecryptField.class) && field.getType().toString().endsWith("String")) {
                            field.setAccessible(true);
                            String fieldValue = (String) field.get(t);
                            if (StringUtils.isNotEmpty(fieldValue)) {
                                field.set(t, AESUtil.decrypt(fieldValue));
                            }
                        }
                    }
                }
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        }
        return t;
    }

    /**
     * 对含注解字段解密
     * @param t
     * @param <T>
     */
    public static <T> void decryptField(T t) {
        Field[] declaredFields = t.getClass().getDeclaredFields();
        try {
            if (declaredFields != null && declaredFields.length > 0) {
                for (Field field : declaredFields) {
                    if (field.isAnnotationPresent(DecryptField.class) && field.getType().toString().endsWith("String")) {
                        field.setAccessible(true);
                        String fieldValue = (String)field.get(t);
                        if(StringUtils.isNotEmpty(fieldValue)) {
                            field.set(t, AESUtil.decrypt(fieldValue));
                        }
                    }
                }
            }
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
        // return t;
    }

    public static Object getDecryptMapValue(Object returnValue, String[] fields){
        return null;
    }

    public static Object getEncryptMapValue(Object parameter, String[] fields) {
        Map<String,Object> map = null;
        try {
            map = (Map<String, Object>) parameter;
        } catch (Exception e) {
            return parameter;
        }
        
       for (String field : fields) {
if (null == map.get(field)) continue;
if (map.get(field) instanceof String) {
String value = String.valueOf(map.get(field));
if (Strings.EMPTY.equals(value)) continue;
for (Map.Entry<String, Object> entry : map.entrySet()) {
if (value.equals(entry.getValue())) {
map.put(entry.getKey(), AESUtil.encrypt(value));
}
}
}
}
return map;
    }

    /**
     * 判断是否需要加密解密的类
     *
     * @param @param  t
     * @param @return
     * @return Boolean
     * @throws
     */
    public static <T> Boolean isEncryptAndDecrypt(T t) {
        return true;
    }


    /**
     * 隐藏号码中间4位
     * @param t
     * @param <T>
     */
    public static <T> void hidePhone(T t) {
        Field[] declaredFields = t.getClass().getDeclaredFields();
        try {
            if (declaredFields != null && declaredFields.length > 0) {
                for (Field field : declaredFields) {
                    if (field.isAnnotationPresent(DecryptField.class) && field.getType().toString().endsWith("String")) {
                        field.setAccessible(true);
                        String fieldValue = (String)field.get(t);
                        if(StringUtils.isNotEmpty(fieldValue)) {
                            // 暂时与解密注解共用一个注解,该注解隐藏手机号中间四位
                            field.set(t, StringUtils.overlay(fieldValue, "****", 3, 7));
                        }
                    }
                }
            }
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }
}    
复制代码

 (注) 将拦截加入配置,不加不生效

    @Bean
    public String myInterceptor(SqlSessionFactory sqlSessionFactory) {
        sqlSessionFactory.getConfiguration().addInterceptor(new DataInterceptor());
        return "myInterceptor";
    }

 

四、使用如下

1.实体使用:在属性上加上加密和解密注解

 

2.单个入参数使用:在单个参数上加上参数

 

 

 

3.多个入参数使用,按照顺序加入记住,这块没有想好怎么实现

 

 

 

结束~~~~~~~,希望能够给到你帮助!!!

2021-08-18 11:32:38

     

posted on   程老の  阅读(2930)  评论(0编辑  收藏  举报
编辑推荐:
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
阅读排行:
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战

< 2025年2月 >
26 27 28 29 30 31 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 1
2 3 4 5 6 7 8
点击右上角即可分享
微信分享提示