求求你别乱脱敏了!MyBatis 插件 + 注解轻松实现数据脱敏,So easy~! ...

打印 上一主题 下一主题

主题 1004|帖子 1004|积分 3012

问题

在项目中需要对用户敏感数据进行脱敏处理,例如身份号、手机号等信息进行加密再入库。
解决思路


  • 就是:一种最简单直接的方式,在所有涉及数据敏感的查询到对插入时进行密码加解密
  • 方法二:有方法一到出现对所有重大问题的影响,需要考虑到问题的出现,并且需要考虑可能出现的组员时添加数据的方法。
最后决定采用mybatis的插件在mybatis的SQL执行和结果填充操作上进行切入。上层业务调用不再需要考虑数据的加敏同时也保证了数据的加解密
Mybatis 插件原理

Mybatis 的是通过拦截器实现的,Mabatis 支持对当事人进行拦截

实现


  • 设置对参数中带有敏感参数字段的数据时进行加密
  • 对返回的结果进行解密处理
根据不同的要求,我们只需要对ParameterHandler和ResultSetHandler进行切入。
定义特定注解,在切入时需要检查字段中是否包含注解来是否加解密
加注解

定义SensitiveData注解
  1. import java.lang.annotation.*;
  2. /**
  3. * 该注解定义在类上
  4. * 插件通过扫描类对象是否包含这个注解来决定是否继续扫描其中的字段注解
  5. * 这个注解要配合EncryptTransaction注解
  6. **/
  7. @Inherited
  8. @Target({ElementType.TYPE})
  9. @Retention(RetentionPolicy.RUNTIME)
  10. public @interface SensitiveData {
  11. }
复制代码
定义EncryptTransaction注解
  1. import java.lang.annotation.*;
  2. /**
  3. * 该注解有两种使用方式
  4. * ①:配合@SensitiveData加在类中的字段上
  5. * ②:直接在Mapper中的方法参数上使用
  6. **/
  7. @Documented
  8. @Inherited
  9. @Target({ElementType.FIELD, ElementType.PARAMETER})
  10. @Retention(RetentionPolicy.RUNTIME)
  11. public @interface EncryptTransaction {
  12. }
复制代码
加解密工具类

解密
  1. package sicnu.cs.ich.common.interceptor.transaction.service;
  2. public interface IDecryptUtil {
  3.     /**
  4.      * 解密
  5.      *
  6.      * @param result resultType的实例
  7.      * @return T
  8.      * @throws IllegalAccessException 字段不可访问异常
  9.      */
  10.     <T> T decrypt(T result) throws IllegalAccessException;
  11. }
复制代码
加密接口
  1. package sicnu.cs.ich.common.interceptor.transaction.service;
  2. import java.lang.reflect.Field;
  3. public interface IEncryptUtil {
  4.     /**
  5.      * 加密
  6.      *
  7.      * @param declaredFields 加密字段
  8.      * @param paramsObject   对象
  9.      * @param <T>            入参类型
  10.      * @return 返回加密
  11.      * @throws IllegalAccessException 不可访问
  12.      */
  13.     <T> T encrypt(Field[] declaredFields, T paramsObject) throws IllegalAccessException;
  14. }
  15. package sicnu.cs.ich.common.interceptor.transaction.service.impl;
  16. import org.springframework.stereotype.Component;
  17. import sicnu.cs.ich.api.common.annotations.transaction.EncryptTransaction;
  18. import sicnu.cs.ich.common.interceptor.transaction.service.IDecryptUtil;
  19. import sicnu.cs.ich.common.util.keyCryptor.DBAESUtil;
  20. import java.lang.reflect.Field;
  21. import java.util.Objects;
  22. @Component
  23. public class DecryptImpl implements IDecryptUtil {
  24.     /**
  25.      * 解密
  26.      *
  27.      * @param result resultType的实例
  28.      */
  29.     @Override
  30.     public <T> T decrypt(T result) throws IllegalAccessException {
  31.         //取出resultType的类
  32.         Class<?> resultClass = result.getClass();
  33.         Field[] declaredFields = resultClass.getDeclaredFields();
  34.         for (Field field : declaredFields) {
  35.             //取出所有被DecryptTransaction注解的字段
  36.             EncryptTransaction encryptTransaction = field.getAnnotation(EncryptTransaction.class);
  37.             if (!Objects.isNull(encryptTransaction)) {
  38.                 field.setAccessible(true);
  39.                 Object object = field.get(result);
  40.                 //String的解密
  41.                 if (object instanceof String) {
  42.                     String value = (String) object;
  43.                     //对注解的字段进行逐一解密
  44.                     try {
  45.                         field.set(result, DBAESUtil.decrypt(value));
  46.                     } catch (Exception e) {
  47.                         e.printStackTrace();
  48.                     }
  49.                 }
  50.             }
  51.         }
  52.         return result;
  53.     }
  54. }
复制代码
加密实现类
  1. package sicnu.cs.ich.common.interceptor.transaction.service.impl;
  2. import com.fasterxml.jackson.databind.ObjectReader;
  3. import org.springframework.stereotype.Component;
  4. import sicnu.cs.ich.api.common.annotations.transaction.EncryptTransaction;
  5. import sicnu.cs.ich.common.interceptor.transaction.service.IEncryptUtil;
  6. import sicnu.cs.ich.common.util.keyCryptor.DBAESUtil;
  7. import java.io.ObjectInputStream;
  8. import java.lang.reflect.Field;
  9. import java.util.Arrays;
  10. import java.util.Objects;
  11. import java.util.Random;
  12. @Component
  13. public class EncryptUtilImpl implements IEncryptUtil {
  14.     @Override
  15.     public <T> T encrypt(Field[] declaredFields, T paramsObject) throws IllegalAccessException {
  16.             //取出所有被EncryptTransaction注解的字段
  17.         for (Field field : declaredFields) {
  18.             EncryptTransaction encryptTransaction = field.getAnnotation(EncryptTransaction.class);
  19.             if (!Objects.isNull(encryptTransaction)) {
  20.                 field.setAccessible(true);
  21.                 Object object = field.get(paramsObject);
  22.                 //暂时只实现String类型的加密
  23.                 if (object instanceof String) {
  24.                     String value = (String) object;
  25.                     //加密
  26.                     try {
  27.                         field.set(paramsObject, DBAESUtil.encrypt(value));
  28.                     } catch (Exception e) {
  29.                         e.printStackTrace();
  30.                     }
  31.                 }
  32.             }
  33.         }
  34.         return paramsObject;
  35.     }
  36. }
复制代码
模拟类
  1. package sicnu.cs.ich.common.interceptor.transaction.service.impl;
  2. import org.springframework.stereotype.Component;
  3. import sicnu.cs.ich.api.common.annotations.transaction.EncryptTransaction;
  4. import sicnu.cs.ich.common.interceptor.transaction.service.IDecryptUtil;
  5. import sicnu.cs.ich.common.util.keyCryptor.DBAESUtil;
  6. import java.lang.reflect.Field;
  7. import java.util.Objects;
  8. @Component
  9. public class DecryptImpl implements IDecryptUtil {
  10.     /**
  11.      * 解密
  12.      *
  13.      * @param result resultType的实例
  14.      */
  15.     @Override
  16.     public <T> T decrypt(T result) throws IllegalAccessException {
  17.         //取出resultType的类
  18.         Class<?> resultClass = result.getClass();
  19.         Field[] declaredFields = resultClass.getDeclaredFields();
  20.         for (Field field : declaredFields) {
  21.             //取出所有被DecryptTransaction注解的字段
  22.             EncryptTransaction encryptTransaction = field.getAnnotation(EncryptTransaction.class);
  23.             if (!Objects.isNull(encryptTransaction)) {
  24.                 field.setAccessible(true);
  25.                 Object object = field.get(result);
  26.                 //String的解密
  27.                 if (object instanceof String) {
  28.                     String value = (String) object;
  29.                     //对注解的字段进行逐一解密
  30.                     try {
  31.                         field.set(result, DBAESUtil.decrypt(value));
  32.                     } catch (Exception e) {
  33.                         e.printStackTrace();
  34.                     }
  35.                 }
  36.             }
  37.         }
  38.         return result;
  39.     }
  40. }
复制代码
加解密工具类
  1. package sicnu.cs.ich.common.util.keyCryptor;
  2. import javax.crypto.Cipher;
  3. import javax.crypto.spec.IvParameterSpec;
  4. import javax.crypto.spec.SecretKeySpec;
  5. import java.util.Base64;
  6. public class DBAESUtil {
  7.     private static final String DEFAULT_V = "6859505890402435";
  8.     // 自己填写
  9.     private static final String KEY = "***";
  10.     private static final String ALGORITHM = "AES";
  11.     private static SecretKeySpec getKey() {
  12.         byte[] arrBTmp = DBAESUtil.KEY.getBytes();
  13.         // 创建一个空的16位字节数组(默认值为0)
  14.         byte[] arrB = new byte[16];
  15.         for (int i = 0; i < arrBTmp.length && i < arrB.length; i++) {
  16.             arrB[i] = arrBTmp[i];
  17.         }
  18.         return new SecretKeySpec(arrB, ALGORITHM);
  19.     }
  20.     /**
  21.      * 加密
  22.      */
  23.     public static String encrypt(String content) throws Exception {
  24.         final Base64.Encoder encoder = Base64.getEncoder();
  25.         SecretKeySpec keySpec = getKey();
  26.         Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
  27.         IvParameterSpec iv = new IvParameterSpec(DEFAULT_V.getBytes());
  28.         cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv);
  29.         byte[] encrypted = cipher.doFinal(content.getBytes());
  30.         return encoder.encodeToString(encrypted);
  31.     }
  32.     /**
  33.      * 解密
  34.      */
  35.     public static String decrypt(String content) throws Exception {
  36.         final Base64.Decoder decoder = Base64.getDecoder();
  37.         SecretKeySpec keySpec = getKey();
  38.         Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
  39.         IvParameterSpec iv = new IvParameterSpec(DEFAULT_V.getBytes());
  40.         cipher.init(Cipher.DECRYPT_MODE, keySpec, iv);
  41.         byte[] base64 = decoder.decode(content);
  42.         byte[] original = cipher.doFinal(base64);
  43.         return new String(original);
  44.     }
  45. }
复制代码
推荐一个开源免费的 Spring Boot 最全教程:
https://github.com/javastacks/spring-boot-best-practice
插件实现

参数插件ParameterInterceptor

切入mybatis设置参数时对敏感数据进行加密
Mybatis插件的使用就是通过实现Mybatis中的Interceptor接口
再@Intercepts注解
  1. // 使用mybatis插件时需要定义签名
  2. // type标识需要切入的Handler
  3. // method表示要要切入的方法
  4. @Intercepts({
  5. @Signature(type = ParameterHandler.class, method = “setParameters”, args = PreparedStatement.class),
  6. })
  7. package sicnu.cs.ich.common.interceptor.transaction;
  8. import com.baomidou.mybatisplus.core.MybatisParameterHandler;
  9. import lombok.extern.slf4j.Slf4j;
  10. import org.apache.ibatis.executor.parameter.ParameterHandler;
  11. import org.apache.ibatis.mapping.BoundSql;
  12. import org.apache.ibatis.mapping.MappedStatement;
  13. import org.apache.ibatis.plugin.*;
  14. import org.springframework.beans.factory.annotation.Autowired;
  15. import org.springframework.core.annotation.AnnotationUtils;
  16. import org.springframework.stereotype.Component;
  17. import org.springframework.util.CollectionUtils;
  18. import sicnu.cs.ich.api.common.annotations.transaction.EncryptTransaction;
  19. import sicnu.cs.ich.api.common.annotations.transaction.SensitiveData;
  20. import sicnu.cs.ich.common.interceptor.transaction.service.IEncryptUtil;
  21. import sicnu.cs.ich.common.util.keyCryptor.DBAESUtil;
  22. import java.lang.annotation.Annotation;
  23. import java.lang.reflect.Field;
  24. import java.lang.reflect.Method;
  25. import java.lang.reflect.Parameter;
  26. import java.sql.PreparedStatement;
  27. import java.util.*;
  28. @Slf4j
  29. // 注入Spring
  30. @Component
  31. @Intercepts({
  32.         @Signature(type = ParameterHandler.class, method = "setParameters", args = PreparedStatement.class),
  33. })
  34. public class ParameterInterceptor implements Interceptor {
  35.     @Autowired
  36.     private IEncryptUtil IEncryptUtil;
  37.     @Override
  38.     public Object intercept(Invocation invocation) throws Throwable {
  39.         //@Signature 指定了 type= parameterHandler 后,这里的 invocation.getTarget() 便是parameterHandler
  40.         //若指定ResultSetHandler ,这里则能强转为ResultSetHandler
  41.         MybatisParameterHandler parameterHandler = (MybatisParameterHandler) invocation.getTarget();
  42.         // 获取参数对像,即 mapper 中 paramsType 的实例
  43.         Field parameterField = parameterHandler.getClass().getDeclaredField("parameterObject");
  44.         parameterField.setAccessible(true);
  45.         //取出实例
  46.         Object parameterObject = parameterField.get(parameterHandler);
  47.         // 搜索该方法中是否有需要加密的普通字段
  48.         List<String> paramNames = searchParamAnnotation(parameterHandler);
  49.         if (parameterObject != null) {
  50.             Class<?> parameterObjectClass = parameterObject.getClass();
  51.             //对类字段进行加密
  52.             //校验该实例的类是否被@SensitiveData所注解
  53.             SensitiveData sensitiveData = AnnotationUtils.findAnnotation(parameterObjectClass, SensitiveData.class);
  54.             if (Objects.nonNull(sensitiveData)) {
  55.                 //取出当前当前类所有字段,传入加密方法
  56.                 Field[] declaredFields = parameterObjectClass.getDeclaredFields();
  57.                 IEncryptUtil.encrypt(declaredFields, parameterObject);
  58.             }
  59.             // 对普通字段进行加密
  60.             if (!CollectionUtils.isEmpty(paramNames)) {
  61.                 // 反射获取 BoundSql 对象,此对象包含生成的sql和sql的参数map映射
  62.                 Field boundSqlField = parameterHandler.getClass().getDeclaredField("boundSql");
  63.                 boundSqlField.setAccessible(true);
  64.                 BoundSql boundSql = (BoundSql) boundSqlField.get(parameterHandler);
  65.                 PreparedStatement ps = (PreparedStatement) invocation.getArgs()[0];
  66.                 // 改写参数
  67.                 processParam(parameterObject, paramNames);
  68.                 // 改写的参数设置到原parameterHandler对象
  69.                 parameterField.set(parameterHandler, parameterObject);
  70.                 parameterHandler.setParameters(ps);
  71.             }
  72.         }
  73.         return invocation.proceed();
  74.     }
  75.     private void processParam(Object parameterObject, List<String> params) throws Exception {
  76.         // 处理参数对象  如果是 map 且map的key 中没有 tenantId,添加到参数map中
  77.         // 如果参数是bean,反射设置值
  78.         if (parameterObject instanceof Map) {
  79.             @SuppressWarnings("unchecked")
  80.             Map<String, String> map = ((Map<String, String>) parameterObject);
  81.             for (String param : params) {
  82.                 String value = map.get(param);
  83.                 map.put(param, value==null?null:DBAESUtil.encrypt(value));
  84.             }
  85. //            parameterObject = map;
  86.         }
  87.     }
  88.     private List<String> searchParamAnnotation(ParameterHandler parameterHandler) throws NoSuchFieldException, ClassNotFoundException, IllegalAccessException {
  89.         Class<MybatisParameterHandler> handlerClass = MybatisParameterHandler.class;
  90.         Field mappedStatementFiled = handlerClass.getDeclaredField("mappedStatement");
  91.         mappedStatementFiled.setAccessible(true);
  92.         MappedStatement mappedStatement = (MappedStatement) mappedStatementFiled.get(parameterHandler);
  93.         String methodName = mappedStatement.getId();
  94.         Class<?> mapperClass = Class.forName(methodName.substring(0, methodName.lastIndexOf('.')));
  95.         methodName = methodName.substring(methodName.lastIndexOf('.') + 1);
  96.         Method[] methods = mapperClass.getDeclaredMethods();
  97.         Method method = null;
  98.         for (Method m : methods) {
  99.             if (m.getName().equals(methodName)) {
  100.                 method = m;
  101.                 break;
  102.             }
  103.         }
  104.         List<String> paramNames = null;
  105.         if (method != null) {
  106.             Annotation[][] pa = method.getParameterAnnotations();
  107.             Parameter[] parameters = method.getParameters();
  108.             for (int i = 0; i < pa.length; i++) {
  109.                 for (Annotation annotation : pa[i]) {
  110.                     if (annotation instanceof EncryptTransaction) {
  111.                         if (paramNames == null) {
  112.                             paramNames = new ArrayList<>();
  113.                         }
  114.                         paramNames.add(parameters[i].getName());
  115.                     }
  116.                 }
  117.             }
  118.         }
  119.         return paramNames;
  120.     }
  121.     @Override
  122.     public Object plugin(Object target) {
  123.         return Plugin.wrap(target, this);
  124.     }
  125.     @Override
  126.     public void setProperties(Properties properties) {
  127.     }
  128. }
复制代码
返回值插件ResultSetInterceptor
  1. package sicnu.cs.ich.common.interceptor.transaction;
  2. import lombok.extern.slf4j.Slf4j;
  3. import org.apache.ibatis.executor.resultset.ResultSetHandler;
  4. import org.apache.ibatis.plugin.*;
  5. import org.springframework.beans.factory.annotation.Autowired;
  6. import org.springframework.core.annotation.AnnotationUtils;
  7. import org.springframework.stereotype.Component;
  8. import org.springframework.util.CollectionUtils;
  9. import sicnu.cs.ich.api.common.annotations.transaction.SensitiveData;
  10. import java.sql.Statement;
  11. import java.util.ArrayList;
  12. import java.util.Objects;
  13. import java.util.Properties;
  14. @Slf4j
  15. @Component
  16. @Intercepts({
  17.         @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})
  18. })
  19. public class ResultSetInterceptor implements Interceptor {
  20.     @Autowired
  21.     private sicnu.cs.ich.common.interceptor.transaction.service.IDecryptUtil IDecryptUtil;
  22.     @Override
  23.     public Object intercept(Invocation invocation) throws Throwable {
  24.         //取出查询的结果
  25.         Object resultObject = invocation.proceed();
  26.         if (Objects.isNull(resultObject)) {
  27.             return null;
  28.         }
  29.         //基于selectList
  30.         if (resultObject instanceof ArrayList) {
  31.             @SuppressWarnings("unchecked")
  32.             ArrayList<Objects> resultList = (ArrayList<Objects>) resultObject;
  33.             if (!CollectionUtils.isEmpty(resultList) && needToDecrypt(resultList.get(0))) {
  34.                 for (Object result : resultList) {
  35.                     //逐一解密
  36.                     IDecryptUtil.decrypt(result);
  37.                 }
  38.             }
  39.             //基于selectOne
  40.         } else {
  41.             if (needToDecrypt(resultObject)) {
  42.                 IDecryptUtil.decrypt(resultObject);
  43.             }
  44.         }
  45.         return resultObject;
  46.     }
  47.     private boolean needToDecrypt(Object object) {
  48.         Class<?> objectClass = object.getClass();
  49.         SensitiveData sensitiveData = AnnotationUtils.findAnnotation(objectClass, SensitiveData.class);
  50.         return Objects.nonNull(sensitiveData);
  51.     }
  52.     @Override
  53.     public Object plugin(Object target) {
  54.         return Plugin.wrap(target, this);
  55.     }
  56.     @Override
  57.     public void setProperties(Properties properties) {
  58.     }
  59. }
复制代码
使用

注意解在实体类上
  1. import lombok.*;
  2. import org.springframework.security.core.userdetails.UserDetails;
  3. import sicnu.cs.ich.api.common.annotations.transaction.EncryptTransaction;
  4. import sicnu.cs.ich.api.common.annotations.transaction.SensitiveData;
  5. @With
  6. @Builder
  7. @Data
  8. @NoArgsConstructor
  9. @AllArgsConstructor
  10. @SensitiveData // 插件只对加了该注解的类进行扫描,只有加了这个注解的类才会生效
  11. public class User implements Serializable {
  12.     private Integer id;
  13.     private String username;
  14.     private String openId;
  15.     private String password;
  16.     // 表明对该字段进行加密
  17.     @EncryptTransaction
  18.     private String email;
  19.     // 表明对该字段进行加密
  20.     @EncryptTransaction
  21.     private String mobile;
  22.     private Date createTime;
  23.     private Date expireTime;
  24.     private Boolean status = true;
  25. }
复制代码
注解在参数上
  1. import org.apache.ibatis.annotations.Mapper;
  2. import org.apache.ibatis.annotations.Param;
  3. import sicnu.cs.ich.api.common.annotations.transaction.EncryptTransaction;
  4. @Mapper
  5. public interface UserMapper extends BaseMapper<User> {
  6.    // 只需要在参数前加上@EncryptTransaction 即可
  7.    long countByEmail(@EncryptTransaction @Param("email") String email);
  8.    long countByMobile(@EncryptTransaction @Param("mobile") String mobile);
  9. }
复制代码
版权声明:本文为CSDN博主「shenyang1026」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/relosy/article/details/123494036
近期热文推荐:
1.1,000+ 道 Java面试题及答案整理(2022最新版)
2.劲爆!Java 协程要来了。。。
3.Spring Boot 2.x 教程,太全了!
4.别再写满屏的爆爆爆炸类了,试试装饰器模式,这才是优雅的方式!!
5.《Java开发手册(嵩山版)》最新发布,速速下载!
觉得不错,别忘了随手点赞+转发哦!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

河曲智叟

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表