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和偏移量)
* @Date 2019-12-24
*/
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> * @author Yampery * @date 2017/10/24 13:05 */ @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> * @author Yampery * @date 2017/10/24 13:01 */ @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> * @author cheng * @date 2021/08/17 13:36 */ 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
