1.使用数据库本身自有的函数进行加密
UPDATE tuc_user SET mobileNo = HEX(AES_ENCRYPT(mobileNo, ‘xxxxxx’));
2.注解类
2.1 SensitiveData
package com.wisedu.campuses.sensitive;import java.lang.annotation.*;/*** @author MR.MEI*/
@Inherited
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface SensitiveData {}
2.2 EncryptField
package com.wisedu.campuses.sensitive;import java.lang.annotation.*;/*** @author MR.MEI*/
@Documented
@Inherited
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptField {String value() default "";}
2.3 EncryptClass
package com.wisedu.campuses.sensitive;import java.lang.annotation.*;/*** @author MR.MEI*/
@Documented
@Inherited
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptClass {}
3.加密工具类
3.1 接口
package com.wisedu.campuses.sensitive;import java.lang.reflect.Field;
import java.util.List;/*** @author MR.MEI*/
public interface IEncryptUtil {/*** 加密** @param declaredFields 加密字段* @param paramsObject 对象* @param <T> 入参类型* @return 返回加密* @throws IllegalAccessException 不可访问*/<T> T encrypt(List<Field[]> declaredFields, T paramsObject) throws IllegalAccessException;
}
3.2 实现类
package com.wisedu.campuses.sensitive;import cn.hutool.core.lang.Validator;
import com.wisedu.campuses.utils.DBAESUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import javax.crypto.IllegalBlockSizeException;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Objects;@Slf4j
@Component
public class EncryptUtilImpl implements IEncryptUtil {@Overridepublic <T> T encrypt(List<Field[]> declaredFields, T paramsObject) throws IllegalAccessException {//取出所有被EncryptTransaction注解的字段for (Field[] declaredField : declaredFields) {for (Field field : declaredField) {EncryptField encryptTransaction = field.getAnnotation(EncryptField.class);if (!Objects.isNull(encryptTransaction)) {field.setAccessible(true);Object object = field.get(paramsObject);//暂时只实现String类型的加密if (object instanceof String) {String value = (String) object;//加密try {if(StringUtils.isNotEmpty(value)){//防止重复加密if(Validator.isHex(value) && value.length()>=32){try {String decrypt = DBAESUtil.decrypt(value);if(StringUtils.isNotEmpty(decrypt)){value = decrypt;}}catch (IllegalBlockSizeException e){log.error(e.getMessage(), e);}}field.set(paramsObject, DBAESUtil.encrypt(value));}} catch (Exception e) {e.printStackTrace();}}}}}return paramsObject;}
}
4.解密工具类
4.1 接口
package com.wisedu.campuses.sensitive;/*** @author MR.MEI*/
public interface IDecryptUtil {/*** 解密** @param result resultType的实例* @return T* @throws IllegalAccessException 字段不可访问异常*/<T> T decrypt(T result) throws IllegalAccessException;
}
4.2 实现类
package com.wisedu.campuses.sensitive;import cn.hutool.core.lang.Validator;
import com.google.common.collect.Lists;
import com.wisedu.campuses.utils.DBAESUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Objects;@Component
public class DecryptImpl implements IDecryptUtil {/*** 解密** @param result resultType的实例*/@Overridepublic <T> T decrypt(T result) throws IllegalAccessException {List<Field[]> fieldsList = Lists.newArrayList();//取出resultType的类Class<?> resultClass = result.getClass();while (null != resultClass){fieldsList.add(resultClass.getDeclaredFields());resultClass = resultClass.getSuperclass();}for (Field[] fields : fieldsList) {for (Field field : fields) {EncryptClass encryptClass = field.getAnnotation(EncryptClass.class);if(!Objects.isNull(encryptClass)){if(field.getType() == List.class){Object o = field.get(result);field.setAccessible(true);List filedValue = (List) o;for (Object o1 : filedValue) {decrypt(o1);}}else {field.setAccessible(true);decrypt(field.get(result));}}//取出所有被DecryptTransaction注解的字段EncryptField encryptField = field.getAnnotation(EncryptField.class);if (!Objects.isNull(encryptField)) {field.setAccessible(true);Object object = field.get(result);//String的解密if (object instanceof String) {String value = (String) object;//对注解的字段进行逐一解密try {if(StringUtils.isNotEmpty(value)){if(Validator.isHex(value) && value.length()>=32)field.set(result, DBAESUtil.decrypt(value));}} catch (Exception e) {e.printStackTrace();}}}}}return result;}
}
5.加解密util
package com.wisedu.campuses.utils;import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.symmetric.AES;
import java.nio.charset.StandardCharsets;/*** @author MR.MEI**/
public class DBAESUtil {private static final AES aes;static {aes = SecureUtil.aes("xxxxxx".getBytes(StandardCharsets.UTF_8));}/*** 加密*/public static String encrypt(String content) throws Exception {return aes.encryptHex(content);}/*** 解密*/public static String decrypt(String content) throws Exception {return aes.decryptStr(content);}}
6.参数拦截器
package com.wisedu.campuses.sensitive;import com.google.common.collect.Lists;
import com.wisedu.campuses.utils.DBAESUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.PreparedStatement;
import java.util.*;@Slf4j
@Component
@Intercepts({@Signature(type = ParameterHandler.class, method = "setParameters", args = PreparedStatement.class),
})
public class ParameterInterceptor implements Interceptor {@Autowiredprivate IEncryptUtil IEncryptUtil;@Overridepublic Object intercept(Invocation invocation) throws Throwable {//@Signature 指定了 type= parameterHandler 后,这里的 invocation.getTarget() 便是parameterHandler//若指定ResultSetHandler ,这里则能强转为ResultSetHandlerDefaultParameterHandler parameterHandler = (DefaultParameterHandler) invocation.getTarget();// 获取参数对像,即 mapper 中 paramsType 的实例Field parameterField = parameterHandler.getClass().getDeclaredField("parameterObject");parameterField.setAccessible(true);//取出实例Object parameterObject = parameterField.get(parameterHandler);// 搜索该方法中是否有需要加密的普通字段List<String> paramNames = searchParamAnnotation(parameterHandler);if (parameterObject != null) {if (parameterObject instanceof MapperMethod.ParamMap<?>) {MapperMethod.ParamMap<?> paramMap = (MapperMethod.ParamMap<?>) parameterObject;//支持批量处理if (paramMap.containsKey("list")) {Object objList = paramMap.get("list");if(objList instanceof List){List objs = (List)objList;for (Object obj : objs) {encryptObject(obj);}}}} else {encryptObject(parameterObject);}// 对普通字段进行加密if (!CollectionUtils.isEmpty(paramNames)) {// 反射获取 BoundSql 对象,此对象包含生成的sql和sql的参数map映射Field boundSqlField = parameterHandler.getClass().getDeclaredField("boundSql");boundSqlField.setAccessible(true);BoundSql boundSql = (BoundSql) boundSqlField.get(parameterHandler);PreparedStatement ps = (PreparedStatement) invocation.getArgs()[0];// 改写参数processParam(parameterObject, paramNames);// 改写的参数设置到原parameterHandler对象parameterField.set(parameterHandler, parameterObject);parameterHandler.setParameters(ps);}}return invocation.proceed();}private void encryptObject(Object parameterObject) throws IllegalAccessException {//对类字段进行加密//校验该实例的类是否被@SensitiveData所注解Class<?> parameterObjectClass = parameterObject.getClass();SensitiveData sensitiveData = AnnotationUtils.findAnnotation(parameterObjectClass, SensitiveData.class);if (Objects.nonNull(sensitiveData)) {List<Field[]> fieldsList = Lists.newArrayList();//取出当前当前类所有字段,传入加密方法while (null != parameterObjectClass){fieldsList.add(parameterObjectClass.getDeclaredFields());parameterObjectClass = parameterObjectClass.getSuperclass();}IEncryptUtil.encrypt(fieldsList, parameterObject);}}private void processParam(Object parameterObject, List<String> params) throws Exception {// 处理参数对象 如果是 map 且map的key 中没有 tenantId,添加到参数map中// 如果参数是bean,反射设置值if (parameterObject instanceof Map) {@SuppressWarnings("unchecked")Map<String, String> map = ((Map<String, String>) parameterObject);for (String param : params) {String value = map.get(param);map.put(param, value==null?null: DBAESUtil.encrypt(value));}
// parameterObject = map;}}private List<String> searchParamAnnotation(ParameterHandler parameterHandler) throws NoSuchFieldException, ClassNotFoundException, IllegalAccessException {Class<DefaultParameterHandler> handlerClass = DefaultParameterHandler.class;Field mappedStatementFiled = handlerClass.getDeclaredField("mappedStatement");mappedStatementFiled.setAccessible(true);MappedStatement mappedStatement = (MappedStatement) mappedStatementFiled.get(parameterHandler);String methodName = mappedStatement.getId();Class<?> mapperClass = Class.forName(methodName.substring(0, methodName.lastIndexOf('.')));methodName = methodName.substring(methodName.lastIndexOf('.') + 1);Method[] methods = mapperClass.getDeclaredMethods();Method method = null;for (Method m : methods) {if (m.getName().equals(methodName)) {method = m;break;}}List<String> paramNames = null;if (method != null) {Annotation[][] pa = method.getParameterAnnotations();
// Parameter[] parameters = method.getParameters();for (int i = 0; i < pa.length; i++) {for (Annotation annotation : pa[i]) {if (annotation.annotationType().equals(EncryptField.class)) {EncryptField encryptField = (EncryptField)annotation;if (paramNames == null) {paramNames = new ArrayList<>();}
// paramNames.add(parameters[i].getName());paramNames.add(encryptField.value());}}}}return paramNames;}@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}@Overridepublic void setProperties(Properties properties) {}
}
7.结果集拦截器
package com.wisedu.campuses.sensitive;import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.plugin.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Objects;
import java.util.Properties;@Slf4j
@Component
@Intercepts({@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})
})
public class ResultSetInterceptor implements Interceptor {@Autowiredprivate IDecryptUtil IDecryptUtil;@Overridepublic Object intercept(Invocation invocation) throws Throwable {//取出查询的结果Object resultObject = invocation.proceed();if (Objects.isNull(resultObject)) {return null;}//基于selectListif (resultObject instanceof ArrayList) {@SuppressWarnings("unchecked")ArrayList<Objects> resultList = (ArrayList<Objects>) resultObject;if (!CollectionUtils.isEmpty(resultList) && needToDecrypt(resultList.get(0))) {for (Object result : resultList) {//逐一解密IDecryptUtil.decrypt(result);}}//基于selectOne} else {if (needToDecrypt(resultObject)) {IDecryptUtil.decrypt(resultObject);}}return resultObject;}private boolean needToDecrypt(Object object) {if(null == object){return false;}Class<?> objectClass = object.getClass();SensitiveData sensitiveData = AnnotationUtils.findAnnotation(objectClass, SensitiveData.class);return Objects.nonNull(sensitiveData);}@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}@Overridepublic void setProperties(Properties properties) {}
}
8.配置
<!-- mybaties --><bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"><!-- 加载mybatis的配置文件 --><property name="configLocation" value="classpath:SqlMapConfig.xml"/><!-- 数据源 --><property name="dataSource" ref="dataSource"/><!-- 自动扫描mapping.xml文件 --><property name="mapperLocations"><array><value>classpath*:mapper/**/*.xml</value></array></property><property name="plugins"><array><bean class="com.github.pagehelper.PageInterceptor"><property name="properties"><value>reasonable=true</value></property></bean><bean class="com.wisedu.campuses.sensitive.ParameterInterceptor"/><bean class="com.wisedu.campuses.sensitive.ResultSetInterceptor"/></array></property></bean>
9.具体的测试代码就不贴了,按照下面使用方式,对使用的类添加注解即可

9.1 如果脱敏的字段在类中类,那么就需要使用@EncryptClass,如下图

10.如遇到在业务方法中,对脱敏字段进行了更新后,使用同一对象类进行视图返回的,可以用以下方法作为补充处理,在controller层中增加(根据需要使用,非必要)@DesensitizeSupport注解即可
10.1 注解类
package com.wisedu.campuses.sensitive;import java.lang.annotation.*;@Documented
@Retention(value = RetentionPolicy.RUNTIME)
@Target(value={ElementType.METHOD, ElementType.TYPE})
@Inherited //说明子类可以继承父类中的该注解
public @interface DesensitizeSupport {}
10.2 视图拦截类
package com.wisedu.campuses.handle;import cn.hutool.core.lang.Validator;
import com.wisedu.campuses.sensitive.DesensitizeSupport;
import com.wisedu.campuses.sensitive.EncryptClass;
import com.wisedu.campuses.sensitive.EncryptField;
import com.wisedu.campuses.utils.DBAESUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;/*** 统一处理 返回值/响应体* 脱敏返回试图揭秘加强(可根据实际情况,按需在controller层上加注解)*/
@Slf4j
@ControllerAdvice
public class DesensitizeResponseBodyAdvice implements ResponseBodyAdvice<Object> {@Overridepublic boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {AnnotatedElement annotatedElement = returnType.getAnnotatedElement();//1.首先判断该方法是否存在@DesensitizeSupport注解//2.判断类上是否存在Method method = returnType.getMethod();DesensitizeSupport annotation = method.getAnnotation(DesensitizeSupport.class);DesensitizeSupport clazzSup = method.getDeclaringClass().getAnnotation(DesensitizeSupport.class);return annotation != null || clazzSup != null;}/**** @param body* @param returnType* @param selectedContentType* @param selectedConverterType* @param request* @param response* @return*/@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,Class<? extends HttpMessageConverter<?>> selectedConverterType,ServerHttpRequest request, ServerHttpResponse response) {log.debug("Test ResponseBodyAdvice ==> beforeBodyWrite:" + body.toString() + ";" + returnType);Class<?> childClazz = body.getClass();Field childField = null;
// List filedValue = null;try {//获取数据childField = childClazz.getDeclaredField("data");//设置可访问childField.setAccessible(true);Object objs = childField.get(body);handlerObj(objs);} catch (NoSuchFieldException e) {log.error("未找到数据; message:" + e.getMessage());e.printStackTrace();} catch (IllegalAccessException e) {log.error("处理异常; message:" + e.getMessage());e.printStackTrace();} catch (Exception e){e.printStackTrace();}return body;}private void handlerObj(Object objs) throws Exception {List filedValue = null;if (!(objs instanceof List)) {dealValue(objs);}else {filedValue = (List) objs;//对值进行脱敏for (Object obj : filedValue) {dealValue(obj);}}}public void dealValue(Object obj) throws Exception {Class<?> clazz = obj.getClass();//获取奔雷和父类的属性List<Field> fieldList = getAllFields(clazz);for (Field field : fieldList) {//获取属性上的注解EncryptField encryptFieldAnnotation = field.getAnnotation(EncryptField.class);EncryptClass encryptClassAnnotation = field.getAnnotation(EncryptClass.class);if (encryptFieldAnnotation == null && encryptClassAnnotation == null) {continue;}Class<?> type = field.getType();if(type == List.class){field.setAccessible(true);Object o = field.get(obj);List filedValue = (List) o;for (Object o1 : filedValue) {dealValue(o1);}}//判断属性的类型if (String.class != type) {//非字符串的类型 直接返回continue;}//获取脱敏类型 判断是否脱敏field.setAccessible(true);String oldValue = (String) field.get(obj);if(StringUtils.isNotEmpty(oldValue) && Validator.isHex(oldValue) && oldValue.length()>=32){field.set(obj, DBAESUtil.decrypt(oldValue));}}}/*** 获取所有的字段(包括父类的)* @param clazz* @return*/public List<Field> getAllFields(Class<?> clazz) {List<Field> fieldList = new ArrayList<>();while (clazz != null) {Field[] declaredFields = clazz.getDeclaredFields();fieldList.addAll(Arrays.asList(declaredFields));//获取父类,然后获取父类的属性clazz = clazz.getSuperclass();}return fieldList;}
}
10.3 使用方式
@DesensitizeSupport@RequestMapping(value = "/invoice/detail", method = RequestMethod.POST)@ResponseBodypublic Result detail(@Valid @RequestBody OpenInvoiceQueryDTO openInvoiceQueryDTO, BindingResult bindingResult) {......}










![[VS]网页连接数据库](https://img-blog.csdnimg.cn/f9e5d74128fd45608e620a83893708a5.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5aSn5a2m55Sf56CB5Yac,size_5,color_FFFFFF,t_70,g_se,x_16)




