浏览代码

数据库字段加密解密拦截

gaodm 5 年之前
父节点
当前提交
e96ca18ab4

+ 24 - 0
common/src/main/java/com/diagbot/annotation/CryptField.java

@@ -0,0 +1,24 @@
+package com.diagbot.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * @Description: 加解密注解
+ * @author: gaodm
+ * @time: 2019/12/30 18:36
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
+public @interface CryptField {
+
+    String value() default "";
+
+    boolean encrypt() default true;
+
+    boolean decrypt() default true;
+}

+ 2 - 2
common/src/main/java/com/diagbot/util/AopUtil.java

@@ -4,12 +4,12 @@ import com.diagbot.annotation.BiLogger;
 import com.diagbot.annotation.BiLoggerResult;
 import com.diagbot.annotation.SysLogger;
 import com.diagbot.annotation.SysLoggerExport;
+import com.diagbot.biz.log.entity.BiRecord;
+import com.diagbot.biz.log.entity.SysLog;
 import com.diagbot.dto.RespDTO;
 import com.diagbot.enums.BiSourceEnum;
 import com.diagbot.exception.CommonErrorCode;
 import com.diagbot.exception.CommonException;
-import com.diagbot.biz.log.entity.BiRecord;
-import com.diagbot.biz.log.entity.SysLog;
 import com.diagbot.vo.BaseBiVO;
 import org.aspectj.lang.JoinPoint;
 import org.aspectj.lang.ProceedingJoinPoint;

+ 97 - 0
common/src/main/java/com/diagbot/util/CryptUtil.java

@@ -0,0 +1,97 @@
+package com.diagbot.util;
+
+/**
+ * @Description: 加解密工具类
+ * @author: gaodm
+ * @time: 2019/12/30 11:09
+ */
+public class CryptUtil {
+
+    /**
+     * 加密,把一个字符串在原有的基础上+1
+     *
+     * @param data 需要解密的原字符串
+     * @return 返回解密后的新字符串
+     */
+    public static String encrypt(String data) {
+        try {
+            //把字符串转为字节数组
+            byte[] b = data.getBytes("utf-8");
+            //遍历
+            for (int i = 0; i < b.length; i++) {
+                b[i] += 2;//在原有的基础上+1
+            }
+            return new String(b);
+        } catch (Exception e) {
+            return "";
+        }
+    }
+
+    public static String encrypt_offset(String data) {
+        char[] chars = data.toCharArray();
+        for (int i = 0; i < chars.length; i++) {
+            chars[i] = (char) ((int) chars[i] ^ 7);
+        }
+        return new String(chars);
+    }
+
+    public static String encrypt_char(String data) {
+        char[] chars = data.toCharArray();
+        for (int i = 0; i < chars.length; i++) {
+            chars[i] += 2;
+        }
+        return new String(chars);
+    }
+
+    /**
+     * 解密:把一个加密后的字符串在原有基础上-1
+     *
+     * @param data 加密后的字符串
+     * @return 返回解密后的新字符串
+     */
+    public static String decrypt(String data) {
+        try {
+            //把字符串转为字节数组
+            byte[] b = data.getBytes("utf-8");
+            //遍历
+            for (int i = 0; i < b.length; i++) {
+                b[i] -= 2;//在原有的基础上-1
+            }
+            return new String(b);
+        } catch (Exception e) {
+            return "";
+        }
+    }
+
+    public static String decrypt_char(String data) {
+        char[] chars = data.toCharArray();
+        for (int i = 0; i < chars.length; i++) {
+            chars[i] -= 2;
+        }
+        return new String(chars);
+    }
+
+    public static void main(String[] args) {
+        //加密英文
+        String data = "解密后:�dsfaa2132159-4331";
+        String result = encrypt(data);
+        System.out.println("加密后:" + result);
+        //解密
+        String str = decrypt(result);
+        System.out.println("解密后:" + str);
+
+        String charResult = encrypt_char(data);
+        System.out.println("加密后:" + charResult);
+        //解密
+        String charStr = decrypt_char(charResult);
+        System.out.println("解密后:" + charStr);
+
+
+        //加密中文
+        data = "跳梁小豆tlxd666,";
+        result = encrypt_char(data);
+        System.out.println("加密后:" + result);
+        String str1 = decrypt_char(result);
+        System.out.println("解密后:" + str1);
+    }
+}

+ 443 - 0
knowledgeman-service/src/main/java/com/diagbot/aop/CryptInterceptor.java

@@ -0,0 +1,443 @@
+package com.diagbot.aop;
+
+import com.diagbot.annotation.CryptField;
+import com.diagbot.util.CryptUtil;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.ibatis.binding.MapperMethod;
+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.apache.ibatis.session.defaults.DefaultSqlSession;
+import org.springframework.stereotype.Component;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @Description: 数据库加解密
+ * @author: gaodm
+ * @time: 2019/12/30 18:38
+ */
+@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 })
+})
+@Component
+public class CryptInterceptor implements Interceptor {
+
+    private static final String PARAM = "param";
+
+    private static final String PARAM_TYPE_LIST = "list";
+
+    private static final String PARAM_TYPE_COLLECTION = "collection";
+
+    private static final String MAPPEDSTATEMENT_ID_SEPERATOR = ".";
+
+    /**
+     * 适用于加密判断
+     */
+    private static final ConcurrentHashMap<String, Set<String>> METHOD_PARAM_ANNOTATIONS_MAP = new ConcurrentHashMap<>();
+    /**
+     * 适用于解密判断
+     */
+    private static final ConcurrentHashMap<String, Boolean> METHOD_ANNOTATIONS_MAP = new ConcurrentHashMap<>();
+
+    public CryptInterceptor() {
+
+    }
+
+    @Override
+    public Object intercept(Invocation invocation) throws Throwable {
+        Object[] args = invocation.getArgs();
+        // 入参
+        Object parameter = args[1];
+        MappedStatement statement = (MappedStatement) args[0];
+        // 判断是否需要解析
+        if (!isNotCrypt(parameter)) {
+            // 单参数 string
+            if (parameter instanceof String) {
+                args[1] = stringEncrypt((String) parameter, getParameterAnnotations(statement));
+                // 单参数 list
+            } else if (parameter instanceof DefaultSqlSession.StrictMap) {
+                DefaultSqlSession.StrictMap<Object> strictMap = (DefaultSqlSession.StrictMap<Object>) parameter;
+                for (Map.Entry<String, Object> entry : strictMap.entrySet()) {
+                    if (entry.getKey().contains(PARAM_TYPE_COLLECTION)) {
+                        continue;
+                    }
+                    if (entry.getKey().contains(PARAM_TYPE_LIST)) {
+                        Set<String> set = getParameterAnnotations(statement);
+                        listEncrypt((List) entry.getValue(), !set.isEmpty());
+                    }
+                }
+                // 多参数
+            } else if (parameter instanceof MapperMethod.ParamMap) {
+                MapperMethod.ParamMap<Object> paramMap = (MapperMethod.ParamMap<Object>) parameter;
+                Set<String> set = getParameterAnnotations(statement);
+                boolean setEmpty = set.isEmpty();
+                // 解析每一个参数
+                for (Map.Entry<String, Object> entry : paramMap.entrySet()) {
+                    // 判断不需要解析的类型 不解析map
+                    if (isNotCrypt(entry.getValue()) || entry.getValue() instanceof Map || entry.getKey().contains(PARAM)) {
+                        continue;
+                    }
+                    // 如果string
+                    if (entry.getValue() instanceof String) {
+                        entry.setValue(stringEncrypt(entry.getKey(), (String) entry.getValue(), set));
+                        continue;
+                    }
+                    boolean isSetValue = !setEmpty && set.contains(entry.getKey());
+                    // 如果 list
+                    if (entry.getValue() instanceof List) {
+                        listEncrypt((List) entry.getValue(), isSetValue);
+                        continue;
+                    }
+                    beanEncrypt(entry.getValue());
+                }
+                // bean
+            } else {
+                beanEncrypt(parameter);
+            }
+        }
+
+        // 获得出参
+        Object returnValue = invocation.proceed();
+
+        // 出参解密
+        if (isNotCrypt(returnValue)) {
+            return returnValue;
+        }
+        Boolean bo = getMethodAnnotations(statement);
+        if (returnValue instanceof String && bo) {
+            return stringDecrypt((String) returnValue);
+        }
+        if (returnValue instanceof List) {
+            listDecrypt((List) returnValue, bo);
+            return returnValue;
+        }
+
+        return returnValue;
+    }
+
+    @Override
+    public Object plugin(Object target) {
+        return Plugin.wrap(target, this);
+    }
+
+    @Override
+    public void setProperties(Properties properties) {
+
+    }
+
+    /**
+     * 获取 方法上的注解
+     *
+     * @param statement
+     * @return
+     * @throws ClassNotFoundException
+     */
+    private Boolean getMethodAnnotations(MappedStatement statement) throws ClassNotFoundException {
+        final String id = statement.getId();
+        Boolean bo = METHOD_ANNOTATIONS_MAP.get(id);
+        if (bo != null) {
+            return bo;
+        }
+        Method m = getMethodByMappedStatementId(id);
+        if (m == null) {
+            return Boolean.FALSE;
+        }
+        final CryptField cryptField = m.getAnnotation(CryptField.class);
+        // 如果允许解密
+        if (cryptField != null && cryptField.decrypt()) {
+            bo = Boolean.TRUE;
+        } else {
+            bo = Boolean.FALSE;
+        }
+        Boolean bo1 = METHOD_ANNOTATIONS_MAP.putIfAbsent(id, bo);
+        if (bo1 != null) {
+            bo = bo1;
+        }
+
+        return bo;
+    }
+
+    /**
+     * 获取 方法参数上的注解
+     *
+     * @param statement
+     * @return
+     * @throws ClassNotFoundException
+     */
+    private Set<String> getParameterAnnotations(MappedStatement statement) throws ClassNotFoundException {
+        final String id = statement.getId();
+        Set<String> set = METHOD_PARAM_ANNOTATIONS_MAP.get(id);
+        if (set != null) {
+            return set;
+        }
+        set = new HashSet<>();
+        Method m = getMethodByMappedStatementId(id);
+        if (m == null) {
+            return set;
+        }
+        final Annotation[][] paramAnnotations = m.getParameterAnnotations();
+        // get names from @CryptField annotations
+        for (Annotation[] paramAnnotation : paramAnnotations) {
+            for (Annotation annotation : paramAnnotation) {
+                if (annotation instanceof CryptField) {
+                    CryptField cryptField = (CryptField) annotation;
+                    // 如果允许加密
+                    if (cryptField.encrypt()) {
+                        set.add(cryptField.value());
+                    }
+                    break;
+                }
+            }
+        }
+
+        Set<String> oldSet = METHOD_PARAM_ANNOTATIONS_MAP.putIfAbsent(id, set);
+        if (oldSet != null) {
+            set = oldSet;
+        }
+
+        return set;
+    }
+
+    /**
+     * 通过mappedStatementId get Method
+     *
+     * @param id
+     * @return
+     * @throws ClassNotFoundException
+     */
+    private Method getMethodByMappedStatementId(String id) throws ClassNotFoundException {
+        Method m = null;
+        final Class clazz = Class.forName(id.substring(0, id.lastIndexOf(MAPPEDSTATEMENT_ID_SEPERATOR)));
+        for (Method method : clazz.getMethods()) {
+            if (method.getName().equals(id.substring(id.lastIndexOf(MAPPEDSTATEMENT_ID_SEPERATOR) + 1))) {
+                m = method;
+                break;
+            }
+        }
+
+        return m;
+    }
+
+    /**
+     * 判断是否需要加解密
+     *
+     * @param o
+     * @return
+     */
+    private boolean isNotCrypt(Object o) {
+        return o == null || o instanceof Double || o instanceof Integer || o instanceof Long || o instanceof Boolean;
+    }
+
+    /**
+     * String 加密
+     *
+     * @param str
+     * @return
+     * @throws Exception
+     */
+    private String stringEncrypt(String str) throws Exception {
+        return stringEncrypt(null, str, null, null);
+    }
+
+    /**
+     * String 加密
+     *
+     * @param str
+     * @param set
+     * @return
+     * @throws Exception
+     */
+    private String stringEncrypt(String str, Set<String> set) throws Exception {
+        return stringEncrypt(null, str, set, true);
+    }
+
+    /**
+     * String 加密
+     *
+     * @param name
+     * @param str
+     * @param set
+     * @return
+     * @throws Exception
+     */
+    private String stringEncrypt(String name, String str, Set<String> set) throws Exception {
+        return stringEncrypt(name, str, set, false);
+    }
+
+    /**
+     * String 加密
+     *
+     * @param name
+     * @param str
+     * @param set
+     * @param isSingle
+     * @return
+     * @throws Exception
+     */
+    private String stringEncrypt(String name, String str, Set<String> set, Boolean isSingle) throws Exception {
+        if (StringUtils.isBlank(str)) {
+            return str;
+        }
+        if (isSingle == null) {
+            //todo 加密实现
+            str = CryptUtil.encrypt_char(str);
+            return str;
+        }
+        if (isSingle && set != null && !set.isEmpty()) {
+            //todo 加密实现
+            str = CryptUtil.encrypt_char(str);
+            return str;
+        }
+        if (!isSingle && set != null && !set.isEmpty() && set.contains(name)) {
+            //todo 加密实现
+            str = CryptUtil.encrypt_char(str);
+            return str;
+        }
+
+        return str;
+    }
+
+    /**
+     * String 解密
+     *
+     * @param str
+     * @return
+     */
+    private String stringDecrypt(String str) {
+        if (StringUtils.isBlank(str)) {
+            return str;
+        }
+//        String[] array = str.split("\\|");
+//        if (array.length < 2) {
+//            return str;
+//        }
+        //todo 解密实现
+        str = CryptUtil.decrypt_char(str);
+
+        return str;
+    }
+
+    /**
+     * list 加密
+     *
+     * @param list
+     * @param bo
+     * @return
+     * @throws Exception
+     */
+    private List listEncrypt(List list, Boolean bo) throws Exception {
+        for (int i = 0; i < list.size(); i++) {
+            Object listValue = list.get(i);
+            // 判断不需要解析的类型
+            if (isNotCrypt(listValue) || listValue instanceof Map) {
+                break;
+            }
+            if (listValue instanceof String && bo) {
+                list.set(i, stringEncrypt((String) listValue));
+                continue;
+            }
+            beanEncrypt(listValue);
+        }
+
+        return list;
+    }
+
+    /**
+     * list 解密
+     *
+     * @param list
+     * @param bo
+     * @return
+     * @throws Exception
+     */
+    private List listDecrypt(List list, Boolean bo) throws Exception {
+        for (int i = 0; i < list.size(); i++) {
+            Object listValue = list.get(i);
+            // 判断不需要解析的类型 获得
+            if (isNotCrypt(listValue) || listValue instanceof Map) {
+                break;
+            }
+            if (listValue instanceof String && bo) {
+                list.set(i, stringDecrypt((String) listValue));
+                continue;
+            }
+            beanDecrypt(listValue);
+        }
+
+        return list;
+    }
+
+    /**
+     * bean 加密
+     *
+     * @param val
+     * @throws Exception
+     */
+    private void beanEncrypt(Object val) throws Exception {
+        Class objClazz = val.getClass();
+        Field[] objFields = objClazz.getDeclaredFields();
+        for (Field field : objFields) {
+            CryptField cryptField = field.getAnnotation(CryptField.class);
+            if (cryptField != null && cryptField.encrypt()) {
+                field.setAccessible(true);
+                Object fieldValue = field.get(val);
+                if (fieldValue == null) {
+                    continue;
+                }
+                if (field.getType().equals(String.class)) {
+                    field.set(val, stringEncrypt((String) fieldValue));
+                    continue;
+                }
+                if (field.getType().equals(List.class)) {
+                    field.set(val, listEncrypt((List) fieldValue, Boolean.TRUE));
+                    continue;
+                }
+            }
+        }
+    }
+
+    /**
+     * bean 解密
+     *
+     * @param val
+     * @throws Exception
+     */
+    private void beanDecrypt(Object val) throws Exception {
+        Class objClazz = val.getClass();
+        Field[] objFields = objClazz.getDeclaredFields();
+        for (Field field : objFields) {
+            CryptField cryptField = field.getAnnotation(CryptField.class);
+            if (cryptField != null && cryptField.decrypt()) {
+                field.setAccessible(true);
+                Object fieldValue = field.get(val);
+                if (fieldValue == null) {
+                    continue;
+                }
+                if (field.getType().equals(String.class)) {
+                    field.set(val, stringDecrypt((String) fieldValue));
+                    continue;
+                }
+                if (field.getType().equals(List.class)) {
+                    field.set(val, listDecrypt((List) fieldValue, Boolean.TRUE));
+                    continue;
+                }
+            }
+        }
+    }
+}