基于拦截器+mybatis+注解 实现对敏感字段进行加解密

打印 上一主题 下一主题

主题 994|帖子 994|积分 2982

实现:

  •   自定义注解类
  •   自定义myabtis拦截器,拦截mybatis,主要涉及三个handler(StatementHandler,ParameterHandler,ResultSetHandler)
  •   自定义加解密工具类
  •        自定义业务处理Service(根据业务自行开发)
  •   自定义注解添加再实体类及需要加解密字段上进行简单增改查测试


  • 1. 自定义注解类
  1. import java.lang.annotation.*;
  2. /**
  3. * =====================================
  4. * *******开发部
  5. * =====================================
  6. *
  7. * @author 开发者
  8. * @version 1.0-SNAPSHOT
  9. *
  10. * @date 2023/2/6
  11. */
  12. @Target({ElementType.TYPE, ElementType.PARAMETER})
  13. @Retention(RetentionPolicy.RUNTIME)
  14. @Inherited
  15. @Documented
  16. public @interface SensitiveEntity {
  17. }<br><br><br><br>
复制代码
  1. import java.lang.annotation.*;<br><br>/**<br> * =====================================<br> * ***********开发部<br> * =====================================<br> *<br> * @author 开发者<br> * @version 1.0-SNAPSHOT<br> * <br> * @date 2023/2/6<br> */<br>@Target({ElementType.FIELD})<br>@Retention(RetentionPolicy.RUNTIME)<br>@Inherited<br>@Documented<br>public @interface SensitiveField {<br>}
复制代码
  1.  
复制代码

  • 2. 自定义拦截器
  1. import ********.SensitiveEntity;
  2. import ********.AesService;
  3. import lombok.extern.slf4j.Slf4j;
  4. import org.apache.ibatis.executor.parameter.ParameterHandler;
  5. import org.apache.ibatis.executor.resultset.ResultSetHandler;
  6. import org.apache.ibatis.executor.statement.StatementHandler;
  7. import org.apache.ibatis.mapping.BoundSql;
  8. import org.apache.ibatis.plugin.Interceptor;
  9. import org.apache.ibatis.plugin.Intercepts;
  10. import org.apache.ibatis.plugin.Invocation;
  11. import org.apache.ibatis.plugin.Signature;
  12. import org.springframework.core.annotation.AnnotationUtils;
  13. import org.springframework.stereotype.Component;
  14. import org.springframework.util.CollectionUtils;
  15. import javax.annotation.Resource;
  16. import java.lang.reflect.Field;
  17. import java.sql.Connection;
  18. import java.sql.PreparedStatement;
  19. import java.sql.Statement;
  20. import java.util.ArrayList;
  21. import java.util.Objects;
  22. import java.util.Properties;
  23. /**
  24. * =====================================
  25. * ***********开发部
  26. * =====================================
  27. *
  28. * @version 1.0-SNAPSHOT
  29. * @description
  30. * @date 2023/2/10
  31. */
  32. @Component
  33. @Intercepts({
  34.         @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}),
  35.         @Signature(type = ParameterHandler.class, method = "setParameters", args = PreparedStatement.class),
  36.         @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})
  37. })
  38. @Slf4j
  39. public class MyBatisInterceptor implements Interceptor {
  40.     @Resource
  41.     private AesService aesService;
  42.     @Override
  43.     public Object intercept(Invocation invocation) throws Throwable {
  44.         Object target = invocation.getTarget();
  45.         //拦截sql结果处理器
  46.         if (target instanceof ResultSetHandler) {
  47.             return resultDecrypt(invocation);
  48.         }
  49.         //拦截sql参数处理器
  50.         if (target instanceof ParameterHandler) {
  51.             return parameterEncrypt(invocation);
  52.         }
  53.         //拦截sql语句处理器
  54.         if (target instanceof StatementHandler) {
  55.             return replaceSql(invocation);
  56.         }
  57.         return invocation.proceed();
  58.     }
  59.     /**
  60.      * 对mybatis映射结果进行字段解密
  61.      *
  62.      * @param invocation 参数
  63.      * @return 结果
  64.      * @throws Throwable 异常
  65.      */
  66.     private Object resultDecrypt(Invocation invocation) throws Throwable {
  67.         //取出查询的结果
  68.         Object resultObject = invocation.proceed();
  69.         if (Objects.isNull(resultObject)) {
  70.             return null;
  71.         }
  72.         //基于selectList
  73.         if (resultObject instanceof ArrayList) {
  74.             ArrayList resultList = (ArrayList) resultObject;
  75.             if (CollectionUtils.isEmpty(resultList) || !needToDecrypt(resultList.get(0))) {
  76.                 return resultObject;
  77.             }
  78.             for (Object result : resultList) {
  79.                 //逐一解密
  80.                 aesService.decrypt(result);
  81.             }
  82.             //基于selectOne
  83.         } else {
  84.             if (needToDecrypt(resultObject)) {
  85.                 aesService.decrypt(resultObject);
  86.             }
  87.         }
  88.         return resultObject;
  89.     }
  90.     /**
  91.      * mybatis映射参数进行加密
  92.      *
  93.      * @param invocation 参数
  94.      * @return 结果
  95.      * @throws Throwable 异常
  96.      */
  97.     private Object parameterEncrypt(Invocation invocation) throws Throwable {
  98.         //@Signature 指定了 type= parameterHandler 后,这里的 invocation.getTarget() 便是parameterHandler
  99.         //若指定ResultSetHandler ,这里则能强转为ResultSetHandler
  100.         ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();
  101.         // 获取参数对像,即 mapper 中 paramsType 的实例
  102.         Field parameterField = parameterHandler.getClass().getDeclaredField("parameterObject");
  103.         parameterField.setAccessible(true);
  104.         //取出实例
  105.         Object parameterObject = parameterField.get(parameterHandler);
  106.         if (null == parameterObject) {
  107.             return invocation.proceed();
  108.         }
  109.         Class<?> parameterObjectClass = parameterObject.getClass();
  110.         //校验该实例的类是否被@SensitiveEntity所注解
  111.         SensitiveEntity sensitiveEntity = AnnotationUtils.findAnnotation(parameterObjectClass, SensitiveEntity.class);
  112.         //未被@SensitiveEntity所注解 则为null
  113.         if (Objects.isNull(sensitiveEntity)) {
  114.             return invocation.proceed();
  115.         }
  116.         //取出当前当前类所有字段,传入加密方法
  117.         Field[] declaredFields = parameterObjectClass.getDeclaredFields();
  118.         aesService.encrypt(declaredFields, parameterObject);
  119.         return invocation.proceed();
  120.     }
  121.     /**
  122.      * 替换mybatis Sql中的加密Key
  123.      *
  124.      * @param invocation 参数
  125.      * @return 结果
  126.      * @throws Throwable 异常
  127.      */
  128.     private Object replaceSql(Invocation invocation) throws Throwable {
  129.         StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
  130.         BoundSql boundSql = statementHandler.getBoundSql();
  131.         //获取到原始sql语句
  132.         String sql = boundSql.getSql();
  133.         sql = aesService.replaceAll(sql);
  134.         if (null == sql){
  135.             return invocation.proceed();
  136.         }
  137.         //通过反射修改sql语句
  138.         Field field = boundSql.getClass().getDeclaredField("sql");
  139.         field.setAccessible(true);
  140.         field.set(boundSql, sql);
  141.         return invocation.proceed();
  142.     }
  143.     /**
  144.      * 判断是否包含需要加解密对象
  145.      *
  146.      * @param object 参数
  147.      * @return 结果
  148.      */
  149.     private boolean needToDecrypt(Object object) {
  150.         Class<?> objectClass = object.getClass();
  151.         SensitiveEntity sensitiveEntity = AnnotationUtils.findAnnotation(objectClass, SensitiveEntity.class);
  152.         return Objects.nonNull(sensitiveEntity);
  153.     }
  154.     @Override
  155.     public Object plugin(Object target) {
  156.         return Interceptor.super.plugin(target);
  157.     }
  158.     @Override
  159.     public void setProperties(Properties properties) {
  160.         Interceptor.super.setProperties(properties);
  161.     }
  162. }
复制代码

  • 3. 自定义解密工具类
  1. import lombok.extern.slf4j.Slf4j;
  2. import org.springframework.beans.factory.annotation.Value;
  3. import org.springframework.stereotype.Component;
  4. import org.springframework.util.Base64Utils;
  5. import javax.crypto.Cipher;
  6. import javax.crypto.spec.SecretKeySpec;
  7. import java.util.regex.Matcher;
  8. import java.util.regex.Pattern;
  9. /**
  10. * =====================================
  11. * ****************开发部
  12. * =====================================
  13. *
  14. * @author 开发者
  15. * @version 1.0-SNAPSHOT
  16. * @description
  17. * @date 2023/2/10
  18. */
  19. @Component
  20. @Slf4j
  21. public class AesFieldUtils {
  22.     /**
  23.      * 加密算法
  24.      */
  25.     private final String KEY_ALGORITHM = "AES";
  26.     /**
  27.      * 算法/模式/补码方式
  28.      */
  29.     private final String DEFAULT_CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding";
  30.     /**
  31.      * 编码格式
  32.      */
  33.     private final String CODE = "utf-8";
  34.     /**
  35.      * base64验证规则
  36.      */
  37.     private final String BASE64_RULE = "^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{4}|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)$";
  38.     /**
  39.      * 正则验证对象
  40.      */
  41.     private final Pattern PATTERN = Pattern.compile(BASE64_RULE);
  42.     /**
  43.      * 加解密 密钥key
  44.      */
  45.     @Value("${encrypt.key}")
  46.     public static String encryptKey;
  47.     /**
  48.      * @param content 加密字符串
  49.      * @return 加密结果
  50.      */
  51.     public String encrypt(String content) {
  52.         return encrypt(content, encryptKey);
  53.     }
  54.     /**
  55.      * 加密
  56.      *
  57.      * @param content 加密参数
  58.      * @param key     加密key
  59.      * @return 结果字符串
  60.      */
  61.     public String encrypt(String content, String key) {
  62.         //判断如果已经是base64加密字符串则返回原字符串
  63.         if (isBase64(content)) {
  64.             return content;
  65.         }
  66.         byte[] encrypted = encrypt2bytes(content, key);
  67.         if (null == encrypted || encrypted.length < 1) {
  68.             log.info("加密字符串[{}]转字节为null", content);
  69.             return null;
  70.         }
  71.         return Base64Utils.encodeToString(encrypted);
  72.     }
  73.     /**
  74.      * @param content 加密字符串
  75.      * @param key     加密key
  76.      * @return 返回加密字节
  77.      */
  78.     public byte[] encrypt2bytes(String content, String key) {
  79.         try {
  80.             byte[] raw = key.getBytes(CODE);
  81.             SecretKeySpec secretKeySpec = new SecretKeySpec(raw, KEY_ALGORITHM);
  82.             Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);
  83.             cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
  84.             return cipher.doFinal(content.getBytes(CODE));
  85.         } catch (Exception e) {
  86.             log.error("failed to encrypt: {} of {}", content, e);
  87.             return null;
  88.         }
  89.     }
  90.     /**
  91.      * @param content 加密字符串
  92.      * @return 返回加密结果
  93.      */
  94.     public String decrypt(String content) {
  95.         try {
  96.             return decrypt(content, encryptKey);
  97.         } catch (Exception e) {
  98.             log.error("failed to decrypt: {}, e: {}", content, e);
  99.             return null;
  100.         }
  101.     }
  102.     /**
  103.      * 解密
  104.      *
  105.      * @param content 解密字符串
  106.      * @param key     解密key
  107.      * @return 解密结果
  108.      */
  109.     public String decrypt(String content, String key) throws Exception {
  110.         //不是base64格式字符串则不进行解密
  111.         if (!isBase64(content)) {
  112.             return content;
  113.         }
  114.         return decrypt(Base64Utils.decodeFromString(content), key);
  115.     }
  116.     /**
  117.      * @param content 解密字节
  118.      * @param key     解密key
  119.      * @return 返回解密内容
  120.      */
  121.     public String decrypt(byte[] content, String key) throws Exception {
  122.         if (key == null) {
  123.             log.error("AES key should not be null");
  124.             return null;
  125.         }
  126.         byte[] raw = key.getBytes(CODE);
  127.         SecretKeySpec keySpec = new SecretKeySpec(raw, KEY_ALGORITHM);
  128.         Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);
  129.         cipher.init(Cipher.DECRYPT_MODE, keySpec);
  130.         try {
  131.             byte[] original = cipher.doFinal(content);
  132.             return new String(original, CODE);
  133.         } catch (Exception e) {
  134.             log.error("failed to decrypt content: {}/ key: {}, e: {}", content, key, e);
  135.             return null;
  136.         }
  137.     }
  138.     /**
  139.      * 判断是否为 base64加密
  140.      *
  141.      * @param str 参数
  142.      * @return 结果
  143.      */
  144.     private boolean isBase64(String str) {
  145.         Matcher matcher = PATTERN.matcher(str);
  146.         return matcher.matches();
  147.     }
  148. }
复制代码

  • 4. 自定义业务处理Service
  1. import *****************.SensitiveField;
  2. import *****************.AesService;
  3. import *****************.AesFieldUtils;
  4. import *****************.StringUtils;
  5. import org.springframework.beans.factory.annotation.Value;
  6. import org.springframework.stereotype.Service;
  7. import javax.annotation.Resource;
  8. import java.lang.reflect.Field;
  9. import java.util.Objects;
  10. /**
  11. * =====================================
  12. * *****************开发部
  13. * =====================================
  14. *
  15. * @author 开发者
  16. * @version 1.0-SNAPSHOT
  17. * @description
  18. * @date 2023/2/10
  19. */
  20. @Service
  21. public class AesServiceImpl implements AesService {
  22.     @Value("${aes.key}")
  23.     private String key;
  24.     @Value("${aes.keyField}")
  25.     private String keyField;
  26.     @Resource
  27.     private AesFieldUtils aesFieldUtils;
  28.     @Override
  29.     public <T> T encrypt(Field[] declaredFields, T paramsObject) throws Exception {
  30.         for (Field field : declaredFields) {
  31.             //取出所有被EncryptDecryptField注解的字段
  32.             SensitiveField sensitiveField = field.getAnnotation(SensitiveField.class);
  33.             if (Objects.isNull(sensitiveField)) {
  34.                 continue;
  35.             }
  36.             field.setAccessible(true);
  37.             Object object = field.get(paramsObject);
  38.             //暂时只实现String类型的加密
  39.             if (object instanceof String) {
  40.                 String value = (String) object;
  41.                 //如果映射字段值为空,并且以==结尾则跳过不进行加密
  42.                 if (!StringUtils.noEmpty(value)) {
  43.                     continue;
  44.                 }
  45.                 //加密  这里我使用自定义的AES加密工具
  46.                 field.set(paramsObject, aesFieldUtils.encrypt(value, key));
  47.             }
  48.         }
  49.         return paramsObject;
  50.     }
  51.     @Override
  52.     public <T> T decrypt(T result) throws Exception {
  53.         //取出resultType的类
  54.         Class<?> resultClass = result.getClass();
  55.         Field[] declaredFields = resultClass.getDeclaredFields();
  56.         for (Field field : declaredFields) {
  57.             //取出所有被EncryptDecryptField注解的字段
  58.             SensitiveField sensitiveField = field.getAnnotation(SensitiveField.class);
  59.             if (Objects.isNull(sensitiveField)) {
  60.                 continue;
  61.             }
  62.             field.setAccessible(true);
  63.             Object object = field.get(result);
  64.             //只支持String的解密
  65.             if (object instanceof String) {
  66.                 String value = (String) object;
  67.                 //如果映射字段值为空,并且不已==结尾则跳过不进行解密
  68.                 if (!StringUtils.noEmpty(value)) {
  69.                     continue;
  70.                 }
  71.                 //对注解的字段进行逐一解密
  72.                 field.set(result, aesFieldUtils.decrypt(value, key));
  73.             }
  74.         }
  75.         return result;
  76.     }
  77.     @Override
  78.     public String replaceAll(String sql) {
  79.         if (sql.contains(keyField)) {
  80.             return sql.replaceAll(keyField, key);
  81.         }
  82.         return null;
  83.     }
  84. }
复制代码

  • 5. 测试代码
  1. import **************.SensitiveEntity;
  2. import **************.SensitiveField;
  3. import com.baomidou.mybatisplus.annotation.IdType;
  4. import com.baomidou.mybatisplus.annotation.TableId;
  5. import com.baomidou.mybatisplus.annotation.TableName;
  6. import com.baomidou.mybatisplus.extension.activerecord.Model;
  7. @SensitiveEntity
  8. @TableName("t_users")
  9. public class Users extends Model<Users> {
  10.     /**
  11.      *
  12.      * This field was generated by MyBatis Generator.
  13.      * This field corresponds to the database column t_users.id
  14.      *
  15.      * @mbg.generated Wed Feb 01 10:03:44 CST 2023
  16.      */
  17.     @TableId(value = "id", type = IdType.AUTO)
  18.     private Integer id;
  19.     /**
  20.      *
  21.      * This field was generated by MyBatis Generator.
  22.      * This field corresponds to the database column t_users.user_id
  23.      *
  24.      * @mbg.generated Wed Feb 01 10:03:44 CST 2023
  25.      */
  26.     private String userId;
  27.     /**
  28.      *
  29.      * This field was generated by MyBatis Generator.
  30.      * This field corresponds to the database column t_users.user_name
  31.      *
  32.      * @mbg.generated Wed Feb 01 10:03:44 CST 2023
  33.      */
  34.     private String userName;
  35.     /**
  36.      *
  37.      * This field was generated by MyBatis Generator.
  38.      * This field corresponds to the database column t_users.nick_name
  39.      *
  40.      * @mbg.generated Wed Feb 01 10:03:44 CST 2023
  41.      */
  42.     private String nickName;
  43.     /**
  44.      *
  45.      * This field was generated by MyBatis Generator.
  46.      * This field corresponds to the database column t_users.password
  47.      *
  48.      * @mbg.generated Wed Feb 01 10:03:44 CST 2023
  49.      */
  50.     @SensitiveField
  51.     private String password;
  52.     /**
  53.      *
  54.      * This field was generated by MyBatis Generator.
  55.      * This field corresponds to the database column t_users.pwd_duration
  56.      *
  57.      * @mbg.generated Wed Feb 01 10:03:44 CST 2023
  58.      */
  59.     private String pwdDuration;
  60.     @SensitiveField
  61.     private String birth;<br>}<br><br>
复制代码
  1. @Test<br>public void add(){<br>    Users user = new Users();<br>    user.setUserId("test03");<br>    user.setUserName("小明004");<br>    user.setNickName("test03");<br>    user.setPassword("12343454123");<br>    user.setPwdDuration("124124124214");<br><br>    usersService.insert(user);<br>}<br><br>@Test<br>public void update(){<br>    Users user = new Users();<br>    user.setUserId("test03");<br>    user.setUserName("小明03");<br>    user.setNickName("test03");<br>    user.setPassword("1252525125123");<br>    user.setPwdDuration("1252525125121");<br>    usersService.updateByPrimaryKeySelective(user);<br>}<br><br>@Test<br>public void queryTest(){<br>    System.out.println(JSON.toJSON(usersService.selectByPrimaryKey(3)));<br>}
复制代码
本文原创:如有使用请标明出处

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

使用道具 举报

0 个回复

倒序浏览

快速回复

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

本版积分规则

缠丝猫

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表