云南省住房和城乡建设厅网站,互联网公司的经营范围有哪些,免费咨询服务,网站程序建设一、介绍 业务场景中经常会遇到诸如用户手机号#xff0c;身份证号#xff0c;银行卡号#xff0c;邮箱#xff0c;地址#xff0c;密码等等信息#xff0c;属于敏感信息#xff0c;需要保存在数据库中。而很多公司会会要求对数据库中的此类数据进行加密存储。 敏感数据…一、介绍 业务场景中经常会遇到诸如用户手机号身份证号银行卡号邮箱地址密码等等信息属于敏感信息需要保存在数据库中。而很多公司会会要求对数据库中的此类数据进行加密存储。 敏感数据脱敏需要处理的两个问题
查询操作需要对查询的关键字进行加密同时也要对从库中查到的数据进行解密插入和更新操作需要对插入或者更新的数据进行加密然后保存到数据库。
二、解决思路
最简单的方式对代码中涉及的敏感数据接口在查询和插入、更新时进行加解密。缺点是工作量大代码侵入多在mybatis中进行统一拦截上层业务调用不需要再考虑敏感数据的加解密问题。 通过查阅资料发现思路二目前普遍有两种处理方式 采用mybatis插件在mybatis SQL执行和查询结果填充操作上进行切入使用mybatis框架提供的TypeHandler来实现在持久层处理数据 见https://blog.csdn.net/qq_39052947/article/details/128148805采用mybatis的插件在mybatis SQL执行和查询结果填充操作上进行切入 本文介绍第3种方式即使用Mybatis的插件通过拦截器实现敏感数据加解密
三、mybatis插件原理
Mybatis的插件是采用责任链机制通过JDK动态代理来实现的。默认情况下Mybatis允许使用插件来拦截四个对象
Executor执行CURD操作StatementHandler处理sql语句预编译设置参数等相关工作ParameterHandler设置预编译参数用的ResultSetHandler处理结果集。 编写插件需要标识拦截方法和实现拦截逻辑。 标识拦截拦截方法是通过注解org.apache.ibatis.plugin.Intercepts和注解org.apache.ibatis.plugin.Signature实现的。
四、实现
设置参数时对参数中含有敏感字段的数据进行加密对查询返回的结果进行解密处理 基于上面两种要求我们只需要对ParameterHandler和ResultSetHandler进行切入。
定义特定注解在切入时只需要检查字段中是否包含该注解来决定是否加解密
加解密注解 包含两个注解 SensitiveData注解用在实体类上表示此类有些字段需要加密需要结合SensitiveField一起使用SensitiveField注解用在类的字段上或者方法的参数上表示该字段或参数需要加密 1. 定义SensitiveData注解
package com.zsx.annotation;import java.lang.annotation.*;
/*** 该注解定义在类上* 插件通过扫描类对象是否包含这个注解来决定是否继续扫描其中的字段注解* 这个注解要配合SensitiveField注解* author zhousx* create 2023/10/01-22:45**/
Inherited
Target({ElementType.TYPE})
Retention(RetentionPolicy.RUNTIME)
public interface SensitiveData {}2. 定义SensitiveField注解
package com.zsx.annotation;import java.lang.annotation.*;
/*** 该注解有两种使用方式* ①配合SensitiveData加在类中的字段上* ②直接在Mapper中的方法参数上使用* author zhousx* create 2023/10/01-22:45**/
Documented
Inherited
Target({ElementType.FIELD, ElementType.PARAMETER})
Retention(RetentionPolicy.RUNTIME)
public interface SensitiveField {}
插件实现
1. 参数插件ParameterInterceptor
切入mybatis设置参数时对敏感数据进行加密 Mybatis插件的使用就是通过实现Mybatis中的Interceptor接口 再配合Intercepts注解 // 使用mybatis插件时需要定义签名 // type标识需要切入的Handler // method表示要要切入的方法 // args表示要切入的方法的参数 Intercepts({ Signature(type ParameterHandler.class, method “setParameters”, args PreparedStatement.class), }) 上面这个签名就表示切入ParameterHandler类的setParameters(PreparedStatement preparedStatement)方法 ParameterInterceptor .java
package com.zsx.intercepter;import com.baomidou.mybatisplus.core.MybatisParameterHandler;
import com.zsx.annotation.SensitiveField;
import com.zsx.annotation.SensitiveData;
import com.zsx.utils.DBAESUtil;
import lombok.extern.slf4j.Slf4j;
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.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.lang.reflect.Parameter;
import java.sql.PreparedStatement;
import java.util.*;/*** author zhousx* create 2023/10/01-22:45**/
Slf4j
// 注入Spring
Component
Intercepts({Signature(type ParameterHandler.class, method setParameters, args PreparedStatement.class),
})
public class ParameterInterceptor implements Interceptor {Autowiredprivate com.zsx.utils.IEncryptUtil IEncryptUtil;Overridepublic Object intercept(Invocation invocation) throws Throwable {//Signature 指定了 type parameterHandler 后这里的 invocation.getTarget() 便是parameterHandler//若指定ResultSetHandler 这里则能强转为ResultSetHandlerMybatisParameterHandler parameterHandler (MybatisParameterHandler) invocation.getTarget();// 获取参数对像即 mapper 中 paramsType 的实例Field parameterField parameterHandler.getClass().getDeclaredField(parameterObject);parameterField.setAccessible(true);//取出实例Object parameterObject parameterField.get(parameterHandler);// 搜索该方法中是否有需要加密的普通字段ListString paramNames searchParamAnnotation(parameterHandler);if (parameterObject ! null) {Class? parameterObjectClass parameterObject.getClass();//对类字段进行加密//校验该实例的类是否被SensitiveData所注解SensitiveData sensitiveData AnnotationUtils.findAnnotation(parameterObjectClass, SensitiveData.class);if (Objects.nonNull(sensitiveData)) {//取出当前当前类所有字段传入加密方法Field[] declaredFields parameterObjectClass.getDeclaredFields();IEncryptUtil.encrypt(declaredFields, parameterObject);}//如果传参为List类型对list里的对象进行加密processListParam(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();}/*** 如果传参为List类型对list里的对象判断是否进行加密* param parameterObject* throws IllegalAccessException*/private void processListParam(Object parameterObject) throws IllegalAccessException {// mybatis会把list封装到一个ParamMap中if (parameterObject instanceof Map) {//1. 如果不对传参users使用Param注解则会在map中放入collection、list、users三个键值对这三个指向同一个内存地址即内容相同。if (((Map) parameterObject).containsKey(list)) {Map map (Map) parameterObject;ArrayList list (ArrayList) map.get(list);Object element list.get(0);Class? elementClass element.getClass();SensitiveData tempSensitiveData AnnotationUtils.findAnnotation(elementClass, SensitiveData.class);if (Objects.nonNull(tempSensitiveData)) {for (Object elementObject : list) {Field[] declaredFields elementClass.getDeclaredFields();IEncryptUtil.encrypt(declaredFields, elementObject);}}}//2. 如果使用了Param注解对参数重命名为users那么map中只会放入users、param1两个键值对这2个指向同一个内存地址即内容相同。if (((Map) parameterObject).containsKey(param1)) {Map map (Map) parameterObject;Object param1 map.get(param1);//如果param1是ArrayList,则转为arrayList。否则不转换if (param1 instanceof ArrayList) {ArrayList list (ArrayList) param1;Object element list.get(0);Class? elementClass element.getClass();SensitiveData tempSensitiveData AnnotationUtils.findAnnotation(elementClass, SensitiveData.class);if (Objects.nonNull(tempSensitiveData)) {for (Object elementObject : list) {Field[] declaredFields elementClass.getDeclaredFields();IEncryptUtil.encrypt(declaredFields, elementObject);}}}}}}/*** 处理普通参数对params中的String参数进行加密* param parameterObject* param params* throws Exception*/private void processParam(Object parameterObject, ListString params) throws Exception {// 处理参数对象 如果是 map 且map的key 中没有 tenantId添加到参数map中// 如果参数是bean反射设置值if (parameterObject instanceof Map) {SuppressWarnings(unchecked)MapString, String map ((MapString, String) parameterObject);for (String param : params) {String value map.get(param);map.put(param, value null ? null : DBAESUtil.encrypt(value));}
// parameterObject map;}}/*** 查找参数的注解是否是含有 EncryptTransaction注解如果是则存储参数名* param parameterHandler* return* throws NoSuchFieldException* throws ClassNotFoundException* throws IllegalAccessException*/private ListString searchParamAnnotation(ParameterHandler parameterHandler) throws NoSuchFieldException, ClassNotFoundException, IllegalAccessException {ClassMybatisParameterHandler handlerClass MybatisParameterHandler.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;}}ListString 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 instanceof SensitiveField) {if (paramNames null) {paramNames new ArrayList();}paramNames.add(parameters[i].getName());}}}}return paramNames;}Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}Overridepublic void setProperties(Properties properties) {}
}
2. 返回值插件ResultSetInterceptor
ResultSetInterceptor .java
package com.zsx.intercepter;import com.zsx.annotation.SensitiveData;
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;/*** author zhousx* create 2023/10/01-22:45**/
Slf4j
Component
Intercepts({Signature(type ResultSetHandler.class, method handleResultSets, args {Statement.class})
})
public class ResultSetInterceptor implements Interceptor {Autowiredprivate com.zsx.utils.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)ArrayListObjects resultList (ArrayListObjects) 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) {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) {}
}
加解密工具类
1. 加密接口
IEncryptUtil .java
package com.zsx.utils;import java.lang.reflect.Field;/*** author zhousx* create 2023/10/01-22:45**/
public interface IEncryptUtil {/*** 加密** param declaredFields 加密字段* param paramsObject 对象* param T 入参类型* return 返回加密* throws IllegalAccessException 不可访问*/T T encrypt(Field[] declaredFields, T paramsObject) throws IllegalAccessException;
}2. 解密接口
IDecryptUtil .java
package com.zsx.utils;/*** author zhousx* create 2023/10/01-22:45**/
public interface IDecryptUtil {/*** 解密** param result resultType的实例* return T* throws IllegalAccessException 字段不可访问异常*/T T decrypt(T result) throws IllegalAccessException;
}3. 加密实现类
EncryptImpl .java
package com.zsx.utils;import com.zsx.annotation.SensitiveField;
import org.springframework.stereotype.Component;import java.lang.reflect.Field;
import java.util.Objects;/*** author zhousx* create 2023/10/01-22:45**/
Component
public class EncryptImpl implements IEncryptUtil {Overridepublic T T encrypt(Field[] declaredFields, T paramsObject) throws IllegalAccessException {//取出所有被EncryptTransaction注解的字段for (Field field : declaredFields) {SensitiveField sensitiveField field.getAnnotation(SensitiveField.class);if (!Objects.isNull(sensitiveField)) {field.setAccessible(true);Object object field.get(paramsObject);//暂时只实现String类型的加密if (object instanceof String) {String value (String) object;//修改: 如果有标识则不加密没有则加密并加上标识前缀。防止重复加密String encrypt value;//开始对字段加密使用自定义的AES加密工具try {if(!value.startsWith(DBAESUtil.KEY_SENSITIVE)) {encrypt DBAESUtil.encrypt(value);encrypt DBAESUtil.KEY_SENSITIVE encrypt;}//修改字段值field.set(paramsObject, encrypt);} catch (Exception e) {e.printStackTrace();}}}}return paramsObject;}
}
4. 解密实现类
DecryptImpl.java
package com.zsx.utils;import com.zsx.annotation.SensitiveField;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;import java.lang.reflect.Field;
import java.util.Objects;/*** author zhousx* create 2023/10/01-22:45**/
Component
public class DecryptImpl implements IDecryptUtil {/*** 解密** param result resultType的实例*/Overridepublic T T decrypt(T result) throws IllegalAccessException {//取出resultType的类Class? resultClass result.getClass();Field[] declaredFields resultClass.getDeclaredFields();for (Field field : declaredFields) {//取出所有被DecryptTransaction注解的字段SensitiveField sensitiveField field.getAnnotation(SensitiveField.class);if (!Objects.isNull(sensitiveField)) {field.setAccessible(true);Object object field.get(result);//String的解密if (object instanceof String) {String value (String) object;//对注解的字段进行逐一解密try {//修改没有标识则不解密(防止重复解密)if(value.startsWith(DBAESUtil.KEY_SENSITIVE)) {value value.substring(10);value DBAESUtil.decrypt(value);}//修改字段值field.set(result, value);} catch (Exception e) {e.printStackTrace();}}}}return result;}
}
5. 加解密工具类
package com.zsx.utils;import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;/*** author zhousx* create 2023/10/01-22:45**/
public class DBAESUtil {/*** 加密标识字符串有这个前缀就说明已经加密过*/public static final String KEY_SENSITIVE sensitive_;private static final String DEFAULT_V 6859505890402435;// 自己填写private static final String KEY ***;private static final String ALGORITHM AES;private static SecretKeySpec getKey() {byte[] arrBTmp DBAESUtil.KEY.getBytes();// 创建一个空的16位字节数组默认值为0byte[] arrB new byte[16];for (int i 0; i arrBTmp.length i arrB.length; i) {arrB[i] arrBTmp[i];}return new SecretKeySpec(arrB, ALGORITHM);}/*** 加密*/public static String encrypt(String content) throws Exception {final Base64.Encoder encoder Base64.getEncoder();SecretKeySpec keySpec getKey();Cipher cipher Cipher.getInstance(AES/CBC/PKCS5Padding);IvParameterSpec iv new IvParameterSpec(DEFAULT_V.getBytes());cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv);byte[] encrypted cipher.doFinal(content.getBytes());return encoder.encodeToString(encrypted);}/*** 解密*/public static String decrypt(String content) throws Exception {final Base64.Decoder decoder Base64.getDecoder();SecretKeySpec keySpec getKey();Cipher cipher Cipher.getInstance(AES/CBC/PKCS5Padding);IvParameterSpec iv new IvParameterSpec(DEFAULT_V.getBytes());cipher.init(Cipher.DECRYPT_MODE, keySpec, iv);byte[] base64 decoder.decode(content);byte[] original cipher.doFinal(base64);return new String(original);}}使用
1. 注解在实体类上
package com.zsx.entity;import com.zsx.annotation.SensitiveField;
import com.zsx.annotation.SensitiveData;
import lombok.*;
import java.io.Serializable;/*** author zhousx*/
With
Builder
Data
NoArgsConstructor
AllArgsConstructor
SensitiveData // 插件只对加了该注解的类进行扫描只有加了这个注解的类才会生效
public class User implements Serializable {private Integer id;private String name;// 表明对该字段进行加密SensitiveFieldprivate String email;// 表明对该字段进行加密SensitiveFieldprivate String phone;}2. 注解在参数上
package com.zsx.mapper;import com.zsx.annotation.SensitiveField;
import com.zsx.annotation.SensitiveData;
import com.zsx.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;import java.util.List;Mapper
public interface IUserDao {/*** 测试查询 普通参数加密* param phone* return*/ListUser getUserByPhone(SensitiveFieldParam(phone) String phone);/*** 测试插入 普通参数加密多个需要加密的字段* param name* param email* param phone* return*/int insertUserByParam(Param(name) String name, SensitiveFieldParam(email) String email, SensitiveFieldParam(phone) String phone);
}完整测试用例
UserController.java
/*** Project Name: test-zsx* File Name: UserController* Package Name: com.zsx.controller* Date: 2023/9/13 11:21* Copyright (c) 2023 天翼数字生活科技有限公司 All Rights Reserved.*/
package com.zsx.controller;import com.alibaba.fastjson2.JSON;
import com.zsx.entity.User;
import com.zsx.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.List;/*** description: 测试mybatis拦截器注解 实现数据的自动加解密功能* author: zhoushaoxiong* date: 2023/9/13 11:21*/
RestController
RequestMapping(/user)
public class UserController {Autowiredprivate IUserService userService;/*** 插入一条记录* param user* return*/PostMapping(/add)public String addUser(RequestBody User user){userService.addUser(user);return success;}/*** 查询一条记录* param id* return*/PostMapping(/get/one)public String getUser(Long id){User user userService.getUserById(id);return user.toString();}/*** 查询全部* return*/PostMapping(/get/list)public String getUserAll(){ListUser users userService.getAllUser();return JSON.toJSONString(users);}/*** 通过手机号查询* param phone* return*/PostMapping(/get/phone)public String getUserByPhone(String phone){ListUser users userService.getUserByPhone(phone);return JSON.toJSONString(users);}/*** 通过对象查询* param phone* return*/PostMapping(/get/user/phone)public String getUserByUserPhone(String phone){ListUser users userService.getUser(phone);return JSON.toJSONString(users);}/*** 批量插入* return*/PostMapping(/add/list)public String addUserList(){ListUser users userService.addUserList();return JSON.toJSONString(users);}/*** 插入 dao使用Param注解* return*/PostMapping(/add/user/param)public String addUserParam(){int result userService.addUserByParam();return JSON.toJSONString(result);}
}IUserDao.java
package com.zsx.mapper;import com.zsx.annotation.SensitiveField;
import com.zsx.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;import java.util.List;Mapper
public interface IUserDao {/*** 测试查询 普通参数 拦截器对结果对象进行解密* param id* return*/User getUserById(Long id);/*** 测试插入 传参为对象拦截器对含有加密注解的对象的属性 进行自动加密* param user* return*/int addUser(User user);/*** 测试查询 查询结果为list 需要对list结果里的对象属性进行解密* return*/ListUser getAllUsers();/*** 测试查询 普通参数加密* param phone* return*/ListUser getUserByPhone(SensitiveField Param(phone) String phone);/*** 测试查询 传参为对象 对象中的phone参数需要拦截器进行加密才能查询* param user* return*/ListUser getUserByUser(User user);/*** 测试插入 对list进行加密* param users* return*/int insertBatch(ListUser users);/*** 测试插入 使用Param注解 对list进行加密* param users* return*/int insertBatchByParam(Param(users) ListUser users);/*** 测试插入 普通参数加密多个需要加密的字段* param name* param email* param phone* return*/int insertUserByParam(Param(name) String name, SensitiveField Param(email) String email, SensitiveField Param(phone) String phone);
}
UserMapper.xml
?xml version1.0 encodingUTF-8 ?
!DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//EN http://mybatis.org/dtd/mybatis-3-mapper.dtd
mapper namespacecom.zsx.mapper.IUserDaoselect idgetUserById resultTypecom.zsx.entity.UserSELECT * FROM user WHERE id #{id}/selectselect idgetAllUsers resultTypecom.zsx.entity.Userselect * from user/selectselect idgetUserByPhone resultTypecom.zsx.entity.Userselect * from user where phone #{phone}/selectselect idgetUserByUser parameterTypecom.zsx.entity.User resultTypecom.zsx.entity.Userselect * from user where phone #{phone}/selectinsert idaddUser parameterTypecom.zsx.entity.Userinsert into user (name, email, phone) values (#{name}, #{email}, #{phone})/insertinsert idinsertBatch parameterTypecom.zsx.entity.Userinsert into user (name, email, phone) valuesforeach collectionlist separator, itemitem(#{item.name}, #{item.email}, #{item.phone})/foreach/insertinsert idinsertBatchByParam parameterTypecom.zsx.entity.Userinsert into user (name, email, phone) valuesforeach collectionusers separator, itemitem(#{item.name}, #{item.email}, #{item.phone})/foreach/insertinsert idinsertUserByParaminsert into user (name, email, phone) values (#{name}, #{email}, #{phone})/insert/mapper
引入依赖 dependenciesdependencygroupIdcom.baomidou/groupIdartifactIdmybatis-plus-boot-starter/artifactIdversion3.5.2/version/dependencydependencygroupIdcom.alibaba.fastjson2/groupIdartifactIdfastjson2/artifactIdversion2.0.26/version/dependency/dependencies需要注意的点
1. dao层的传参为List
需要对List中的对象元素进行判断.如果对象是需要加密的则List元素要逐一加密处理。
比如dao层方法 int insertBatch(ListUser users);因此需要对List元素进行判断和处理 //如果传参为List类型对list里的对象进行加密processListParam(parameterObject);/*** 如果传参为List类型对list里的对象判断是否进行加密* param parameterObject* throws IllegalAccessException*/private void processListParam(Object parameterObject) throws IllegalAccessException {// mybatis会把list封装到一个ParamMap中if (parameterObject instanceof Map) {//1. 如果不对传参users使用Param注解则会在map中放入collection、list、users三个键值对这三个指向同一个内存地址即内容相同。if (((Map) parameterObject).containsKey(list)) {Map map (Map) parameterObject;ArrayList list (ArrayList) map.get(list);Object element list.get(0);Class? elementClass element.getClass();SensitiveData tempSensitiveData AnnotationUtils.findAnnotation(elementClass, SensitiveData.class);if (Objects.nonNull(tempSensitiveData)) {for (Object elementObject : list) {Field[] declaredFields elementClass.getDeclaredFields();IEncryptUtil.encrypt(declaredFields, elementObject);}}}//2. 如果使用了Param注解对参数重命名为users那么map中只会放入users、param1两个键值对这2个指向同一个内存地址即内容相同。if (((Map) parameterObject).containsKey(param1)) {Map map (Map) parameterObject;Object param1 map.get(param1);//如果param1是ArrayList,则转为arrayList。否则不转换if (param1 instanceof ArrayList) {ArrayList list (ArrayList) param1;Object element list.get(0);Class? elementClass element.getClass();SensitiveData tempSensitiveData AnnotationUtils.findAnnotation(elementClass, SensitiveData.class);if (Objects.nonNull(tempSensitiveData)) {for (Object elementObject : list) {Field[] declaredFields elementClass.getDeclaredFields();IEncryptUtil.encrypt(declaredFields, elementObject);}}}}}}
这里需要注意不能使用parameterObject instanceof List或parameterObject instanceof ArrayList来判断是否参数是否是列表类型。理由如下
如果没有对dao方法的参数重命名则mybatis会把users、list、collection都是List封装到一个ParamMap中并且list和collection与user指向同一个内存地址即数据完全相同。 int insertBatch(ListUser users);如果使用了Param(users)对dao层的方法参数进行了重命名则mybatis会把list封装到一个ParamMap中这个map中不会有list和collection而是存入当前参数名users和param1的list若有多个参数则继续 user2、param2… int insertBatchByParam(Param(users) ListUser users);2. 防止重复加密或解密
同一个对象在进行过dao层的更新后进行了一次加密后续如果再用该对象进行更新操作又会被加密一次这导致加密了两次而且解密不出错。 解决方法是在加密过的字段前添加加密标识加解密的时候先判断是否被加密过。
比如 Overridepublic int saveOrUpdateUser(){User user new User(31,小二, 111.com, 123411112222);int result userDao.updateUserByPrimaryKey(user);log.info(user: {}, user);if (result ! 1){result userDao.addUser(user);log.info(user: {}, user);}return result;}因此需要加密前需要判断(解密同理)
try {if(!value.startsWith(DBAESUtil.KEY_SENSITIVE)) {encrypt DBAESUtil.encrypt(value);encrypt DBAESUtil.KEY_SENSITIVE encrypt;}//修改字段值field.set(paramsObject, encrypt);
} catch (Exception e) {e.printStackTrace();
} 参考链接 https://blog.csdn.net/relosy/article/details/123494036https://blog.csdn.net/relosy/article/details/123494036https://blog.csdn.net/wtmdcnm/article/details/115211183https://blog.csdn.net/qq_45454294/article/details/122012444