<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          在SpringBoot項(xiàng)目中,自定義注解+攔截器優(yōu)雅的實(shí)現(xiàn)敏感數(shù)據(jù)的加解密!

          共 17517字,需瀏覽 36分鐘

           ·

          2021-05-05 11:55

            作者:CoderTanzJ

          blog.csdn.net/bbcckkl/article/details/104069487

          在實(shí)際生產(chǎn)項(xiàng)目中,經(jīng)常需要對如身份證信息、手機(jī)號、真實(shí)姓名等的敏感數(shù)據(jù)進(jìn)行加密數(shù)據(jù)庫存儲,但在業(yè)務(wù)代碼中對敏感信息進(jìn)行手動加解密則十分不優(yōu)雅,甚至?xí)嬖阱e加密、漏加密、業(yè)務(wù)人員需要知道實(shí)際的加密規(guī)則等的情況。

          本文將介紹使用springboot+mybatis攔截器+自定義注解的形式對敏感數(shù)據(jù)進(jìn)行存儲前攔截加密的詳細(xì)過程。

          一、什么是Mybatis Plugin

          在mybatis官方文檔中,對于Mybatis plugin的的介紹是這樣的:

          MyBatis 允許你在已映射語句執(zhí)行過程中的某一點(diǎn)進(jìn)行攔截調(diào)用。默認(rèn)情況下,MyBatis 允許使用插件來攔截的方法調(diào)用包括:

          //語句執(zhí)行攔截
          Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)

          // 參數(shù)獲取、設(shè)置時進(jìn)行攔截
          ParameterHandler (getParameterObject, setParameters)

          // 對返回結(jié)果進(jìn)行攔截
          ResultSetHandler (handleResultSets, handleOutputParameters)

          //sql語句攔截
          StatementHandler (prepare, parameterize, batch, update, query)

          簡而言之,即在執(zhí)行sql的整個周期中,我們可以任意切入到某一點(diǎn)對sql的參數(shù)、sql執(zhí)行結(jié)果集、sql語句本身等進(jìn)行切面處理。基于這個特性,我們便可以使用其對我們需要進(jìn)行加密的數(shù)據(jù)進(jìn)行切面統(tǒng)一加密處理了(分頁插件 pageHelper 就是這樣實(shí)現(xiàn)數(shù)據(jù)庫分頁查詢的)。

          二、實(shí)現(xiàn)基于注解的敏感信息加解密攔截器

          2.1 實(shí)現(xiàn)思路

          對于數(shù)據(jù)的加密與解密,應(yīng)當(dāng)存在兩個攔截器對數(shù)據(jù)進(jìn)行攔截操作

          參照官方文檔,因此此處我們應(yīng)當(dāng)使用ParameterHandler攔截器對入?yún)⑦M(jìn)行加密

          使用ResultSetHandler攔截器對出參進(jìn)行解密操作。

          目標(biāo)需要加密、解密的字段可能需要靈活變更,此時我們定義一個注解,對需要加密的字段進(jìn)行注解,那么便可以配合攔截器對需要的數(shù)據(jù)進(jìn)行加密與解密操作了。

          mybatis的interceptor接口有以下方法需要實(shí)現(xiàn)。

          public interface Interceptor {
           
            //主要參數(shù)攔截方法
            Object intercept(Invocation invocation) throws Throwable;
           
            //mybatis插件鏈
            default Object plugin(Object target) {return Plugin.wrap(target, this);}
           
            //自定義插件配置文件方法
            default void setProperties(Properties properties) {}
           
          }

          2.2 定義需要加密解密的敏感信息注解

          定義注解敏感信息類(如實(shí)體類POJO\PO)的注解

          /**
           * 注解敏感信息類的注解
           */

          @Inherited
          @Target({ ElementType.TYPE })
          @Retention(RetentionPolicy.RUNTIME)
          public @interface SensitiveData {
          }

          定義注解敏感信息類中敏感字段的注解

          /**
           * 注解敏感信息類中敏感字段的注解
           */

          @Inherited
          @Target({ ElementType.Field })
          @Retention(RetentionPolicy.RUNTIME)
          public @interface SensitiveField {
          }

          2.3 定義加密接口及其實(shí)現(xiàn)類

          定義加密接口,方便以后拓展加密方法(如AES加密算法拓展支持PBE算法,只需要注入時指定一下便可)

          public interface EncryptUtil {
              
              /**
               * 加密
               *
               * @param declaredFields paramsObject所聲明的字段
               * @param paramsObject   mapper中paramsType的實(shí)例
               * @return T
               * @throws IllegalAccessException 字段不可訪問異常
               */

               <T> encrypt(Field[] declaredFields, T paramsObject) throws IllegalAccessException;
          }

          EncryptUtil 的AES加密實(shí)現(xiàn)類,此處AESUtil為自封裝的AES加密工具,需要的小伙伴可以自行封裝,本文不提供。搜索公眾號Java知音,回復(fù)“2021”,送你一份Java面試題寶典)

          @Component
          public class AESEncrypt implements EncryptUtil {
              
              @Autowired
              AESUtil aesUtil;
           
              /**
               * 加密
               *
               * @param declaredFields paramsObject所聲明的字段
               * @param paramsObject   mapper中paramsType的實(shí)例
               * @return T
               * @throws IllegalAccessException 字段不可訪問異常
               */

              @Override
              public <T> encrypt(Field[] declaredFields, T paramsObject) throws IllegalAccessException {
                  for (Field field : declaredFields) {
                      //取出所有被EncryptDecryptField注解的字段
                      SensitiveField sensitiveField = field.getAnnotation(SensitiveField.class);
                      if (!Objects.isNull(sensitiveField)) {
                          field.setAccessible(true);
                          Object object = field.get(paramsObject);
                          //暫時只實(shí)現(xiàn)String類型的加密
                          if (object instanceof String) {
                              String value = (String) object;
                              //加密  這里我使用自定義的AES加密工具
                              field.set(paramsObject, aesUtil.encrypt(value));
                          }
                      }
                  }
                  return paramsObject;
              }
          }

          2.4 實(shí)現(xiàn)入?yún)⒓用軘r截器

          Myabtis包中的org.apache.ibatis.plugin.Interceptor攔截器接口要求我們實(shí)現(xiàn)以下三個方法

          public interface Interceptor {
           
            //核心攔截邏輯
            Object intercept(Invocation invocation) throws Throwable;
            
            //攔截器鏈
            default Object plugin(Object target) {return Plugin.wrap(target, this);}
           
            //自定義配置文件操作
            default void setProperties(Properties properties) { }
           
          }

          因此,參考官方文檔的示例,我們自定義一個入?yún)⒓用軘r截器。

          @Intercepts 注解開啟攔截器,@Signature 注解定義攔截器的實(shí)際類型。

          @Signature中

          • type 屬性指定當(dāng)前攔截器使用StatementHandler 、ResultSetHandler、ParameterHandler,Executor的一種
          • method 屬性指定使用以上四種類型的具體方法(可進(jìn)入class內(nèi)部查看其方法)。
          • args 屬性指定預(yù)編譯語句

          此處我們使用了 ParameterHandler.setParamters()方法,攔截mapper.xml中paramsType的實(shí)例(即在每個含有paramsType屬性mapper語句中,都執(zhí)行該攔截器,對paramsType的實(shí)例進(jìn)行攔截處理)

          /**
           * 加密攔截器
           * 注意@Component注解一定要加上
           *
           * @author : tanzj
           * @date : 2020/1/19.
           */

          @Slf4j
          @Component
          @Intercepts({
                  @Signature(type = ParameterHandler.classmethod "setParameters", args = PreparedStatement.class),
          })
          public class EncryptInterceptor implements Interceptor 
          {
           
              private final EncryptDecryptUtil encryptUtil;
           
              @Autowired
              public EncryptInterceptor(EncryptDecryptUtil encryptUtil) {
                  this.encryptUtil = encryptUtil;
              }
           
              @Override
             
              @Override
              public Object intercept(Invocation invocation) throws Throwable {
                  //@Signature 指定了 type= parameterHandler 后,這里的 invocation.getTarget() 便是parameterHandler 
                  //若指定ResultSetHandler ,這里則能強(qiáng)轉(zhuǎn)為ResultSetHandler
                  ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();
                  // 獲取參數(shù)對像,即 mapper 中 paramsType 的實(shí)例
                  Field parameterField = parameterHandler.getClass().getDeclaredField("parameterObject");
                  parameterField.setAccessible(true);
                  //取出實(shí)例
                  Object parameterObject = parameterField.get(parameterHandler);
                  if (parameterObject != null) {
                      Class<?> parameterObjectClass = parameterObject.getClass();
                      //校驗(yàn)該實(shí)例的類是否被@SensitiveData所注解
                      SensitiveData sensitiveData = AnnotationUtils.findAnnotation(parameterObjectClass, SensitiveData.class);
                      if (Objects.nonNull(sensitiveData)) {
                          //取出當(dāng)前當(dāng)前類所有字段,傳入加密方法
                          Field[] declaredFields = parameterObjectClass.getDeclaredFields();
                          encryptUtil.encrypt(declaredFields, parameterObject);
                      }
                  }
                  return invocation.proceed();
              }
           
              /**
               * 切記配置,否則當(dāng)前攔截器不會加入攔截器鏈
               */

              @Override
              public Object plugin(Object o) {
                  return Plugin.wrap(o, this);
              }
           
              //自定義配置寫入,沒有自定義配置的可以直接置空此方法
              @Override
              public void setProperties(Properties properties) {
              }
          }

          至此完成自定義加密攔截加密。

          2.5 定義解密接口及其實(shí)現(xiàn)類

          解密接口,其中result為mapper.xml中resultType的實(shí)例。

          public interface DecryptUtil {
           
              /**
               * 解密
               *
               * @param result resultType的實(shí)例
               * @return T
               * @throws IllegalAccessException 字段不可訪問異常
               */

               <T> decrypt(T result) throws IllegalAccessException;
              
          }

          解密接口AES工具解密實(shí)現(xiàn)類

          public class AESDecrypt implements DecryptUtil {
              
              @Autowired
              AESUtil aesUtil;
              
              /**
               * 解密
               *
               * @param result resultType的實(shí)例
               * @return T
               * @throws IllegalAccessException 字段不可訪問異常
               */

              @Override
              public <T> decrypt(T result) throws IllegalAccessException {
                  //取出resultType的類
                  Class<?> resultClass = result.getClass();
                  Field[] declaredFields = resultClass.getDeclaredFields();
                  for (Field field : declaredFields) {
                      //取出所有被EncryptDecryptField注解的字段
                      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;
                              //對注解的字段進(jìn)行逐一解密
                              field.set(result, aesUtil.decrypt(value));
                          }
                      }
                  }
                  return result;
              }
          }

          2.6 定義出參解密攔截器

          @Slf4j
          @Component
          @Intercepts({
                  @Signature(type = ResultSetHandler.classmethod "handleResultSets", args = {Statement.class})
          })
          public class DecryptInterceptor implements Interceptor 
          {
           
              @Autowired
              DecryptUtil aesDecrypt;
           
              @Override
              public Object intercept(Invocation invocation) throws Throwable {
                  //取出查詢的結(jié)果
                  Object resultObject = invocation.proceed();
                  if (Objects.isNull(resultObject)) {
                      return null;
                  }
                  //基于selectList
                  if (resultObject instanceof ArrayList) {
                      ArrayList resultList = (ArrayList) resultObject;
                      if (!CollectionUtils.isEmpty(resultList) && needToDecrypt(resultList.get(0))) {
                          for (Object result : resultList) {
                              //逐一解密
                              aesDecrypt.decrypt(result);
                          }
                      }
                  //基于selectOne
                  } else {
                      if (needToDecrypt(resultObject)) {
                          aesDecrypt.decrypt(resultObject);
                      }
                  }
                  return resultObject;
              }
           
              private boolean needToDecrypt(Object object) {
                  Class<?> objectClass = object.getClass();
                  SensitiveData sensitiveData = AnnotationUtils.findAnnotation(objectClass, SensitiveData.class);
                  return Objects.nonNull(sensitiveData);
              }
           
           
              @Override
              public Object plugin(Object target) {
                  return Plugin.wrap(target, this);
              }
           
              @Override
              public void setProperties(Properties properties) {
           
              }
          }

          至此完成解密攔截器的配置工作。

          3、注解實(shí)體類中需要加解密的字段

          此時在mapper中,指定paramType=User resultType=User 便可實(shí)現(xiàn)脫離業(yè)務(wù)層,基于mybatis攔截器的加解密操作。

          END


          順便給大家推薦一個GitHub項(xiàng)目,這個 GitHub 整理了上千本常用技術(shù)PDF,絕大部分核心的技術(shù)書籍都可以在這里找到,

          GitHub地址:https://github.com/javadevbooks/books

          Gitee地址:https://gitee.com/javadevbooks/books

          電子書已經(jīng)更新好了,你們需要的可以自行下載了,記得點(diǎn)一個star,持續(xù)更新中..



          瀏覽 49
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产在线最新地址 | 成人爽a毛片一区二区免费 | 大伊人久久 | 日韩毛片| 99这里只有精品国产 |