<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>

          SpringMVC系列第18篇:強大的RequestBodyAdvice解密

          共 9228字,需瀏覽 19分鐘

           ·

          2021-10-10 07:01


          文末可以領取所有系列高清 pdf。

          大家好,我是路人,這是 SpringMVC 系列第 18 篇。

          1、前言

          在實際項目中,有時候我們需要在請求之前或之后做一些操作,比如:對參數(shù)進行解密,對所有的返回值進行加密等。這些與業(yè)務無關的操作,我們沒有必要在每個 controller 方法中都寫一遍,這里我們就可以使用 springmvc 中的@ControllerAdvice 和 RequestBodyAdvice、ResponseBodyAdvice 來對請求前后進行處理,本質上就是 aop 的思想。

          RequestBodyAdvice:對@RquestBody 進行增強處理,比如所有請求的數(shù)據(jù)都加密之后放在 body 中,在到達 controller 的方法之前,需要先進行解密,那么就可以通過 RequestBodyAdvice 來進行統(tǒng)一的解密處理,無需在 controller 方法中去做這些通用的操作。

          ResponseBodyAdvice:通過名稱就可以知道,這玩意是對@ResponseBody 進行增強處理的,可以對 Controller 中@ResponseBody 類型返回值進行增強處理,也就是說可以攔截@ResponseBody 類型的返回值,進行再次處理,比如加密、包裝等操作。

          本文主要介紹 RequestBodyAdvice 的用法,下一篇介紹 RequestBodyAdvice 的用法。

          2、這個需求如何實現(xiàn)?

          比如咱們的項目中對數(shù)據(jù)的安全性要求比較高,那么可以對所有請求的數(shù)據(jù)進行加密,后端需要解密之后再進行處理。

          怎么實現(xiàn)呢?可以在controller中的每個方法中先進行解密,然后在進行處理,這也太low了吧,需要修改的代碼太多了。

          這個需求可以通過@ControllerAdvice 和 RequestBodyAdvice 來實現(xiàn),特別的簡單,兩三下的功夫就搞定了,下面上代碼。

          3、案例代碼

          3.1、git 代碼位置

          https://gitee.com/javacode2018/springmvc-series

          3.2、自定義一個 RequestBodyAdvice

          package?com.javacode2018.springmvc.chat13.config;

          import?com.javacode2018.springmvc.chat13.util.EncryptionUtils;
          import?org.apache.commons.io.IOUtils;
          import?org.springframework.core.MethodParameter;
          import?org.springframework.http.HttpHeaders;
          import?org.springframework.http.HttpInputMessage;
          import?org.springframework.http.converter.HttpMessageConverter;
          import?org.springframework.web.bind.annotation.ControllerAdvice;
          import?org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter;

          import?java.io.IOException;
          import?java.io.InputStream;
          import?java.lang.reflect.Type;

          @ControllerAdvice
          public?class?DecryptRequestBodyAdvice?extends?RequestBodyAdviceAdapter?{
          ????@Override
          ????public?boolean?supports(MethodParameter?methodParameter,?Type?targetType,?Class>?converterType)?{
          ????????return?true;
          ????}

          ????@Override
          ????public?HttpInputMessage?beforeBodyRead(HttpInputMessage?inputMessage,?MethodParameter?parameter,?Type?targetType,?Class>?converterType)?throws?IOException?{
          ????????String?encoding?=?"UTF-8";
          ????????//①:獲取http請求中原始的body
          ????????String?body?=?IOUtils.toString(inputMessage.getBody(),?encoding);
          ????????//②:解密body,EncryptionUtils源碼在后面
          ????????String?decryptBody?=?EncryptionUtils.desEncrypt(body);
          ????????//將解密之后的body數(shù)據(jù)重新封裝為HttpInputMessage作為當前方法的返回值
          ????????InputStream?inputStream?=?IOUtils.toInputStream(decryptBody,?encoding);
          ????????return?new?HttpInputMessage()?{
          ????????????@Override
          ????????????public?InputStream?getBody()?throws?IOException?{
          ????????????????return?inputStream;
          ????????????}

          ????????????@Override
          ????????????public?HttpHeaders?getHeaders()?{
          ????????????????return?inputMessage.getHeaders();
          ????????????}
          ????????};
          ????}

          }
          • 自定義的類需要實現(xiàn) RequestBodyAdvice 接口,這個接口有個默認的實現(xiàn)類 RequestBodyAdviceAdapter,相當于一個適配器,方法體都是空的,所以我們自定義的類可以直接繼承這個類,更方便一些
          • 這個類上面一定要加上@ControllerAdvice注解,有了這個注解,springmvc 才能夠識別這個類是對 controller 的增強類
          • supports 方法:返回一個 boolean 值,若為 true,則表示參數(shù)需要這個類處理,否則,跳過這個類的處理
          • beforeBodyRead:在 body 中的數(shù)據(jù)讀取之前可以做一些處理,我們在這個方法中來做解密的操作。

          3.3、來個 controller 測試效果

          下面這個 controller 中搞了 2 個方法,稍后我們傳遞密文進來,最后這兩個方法會將結果返回,返回的結果是經(jīng)過DecryptRequestBodyAdvice類處理之后的明文,稍后驗證。

          package?com.javacode2018.springmvc.chat13.controller;

          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;

          @RestController
          public?class?UserController?{
          ????@RequestMapping("/user/add")
          ????public?User?add(@RequestBody?User?user)?{
          ????????System.out.println("user:"?+?user);
          ????????return?user;
          ????}

          ????@RequestMapping("/user/adds")
          ????public?List?adds(@RequestBody?List?userList)?{
          ????????System.out.println("userList:"?+?userList);
          ????????return?userList;
          ????}

          ????public?static?class?User?{
          ????????private?String?name;
          ????????private?Integer?age;

          ????????public?String?getName()?{
          ????????????return?name;
          ????????}

          ????????public?void?setName(String?name)?{
          ????????????this.name?=?name;
          ????????}

          ????????public?Integer?getAge()?{
          ????????????return?age;
          ????????}

          ????????public?void?setAge(Integer?age)?{
          ????????????this.age?=?age;
          ????????}

          ????????@Override
          ????????public?String?toString()?{
          ????????????return?"User{"?+
          ????????????????????"name='"?+?name?+?'\''?+
          ????????????????????",?age="?+?age?+
          ????????????????????'}';
          ????????}
          ????}
          }

          3.4、加密工具類 EncryptionUtils

          可以運行 main 方法,得到 2 個測試的密文。

          package?com.javacode2018.springmvc.chat13.util;


          import?javax.crypto.Cipher;
          import?javax.crypto.spec.IvParameterSpec;
          import?javax.crypto.spec.SecretKeySpec;
          import?java.util.Base64;

          /**
          ?*?加密工具類
          ?*/

          public?class?EncryptionUtils?{

          ????private?static?String?key?=?"abcdef0123456789";

          ????public?static?void?main(String[]?args)?throws?Exception?{
          ????????m1();
          ????????m2();
          ????}

          ????private?static?void?m1(){
          ????????String?body?=?"{\"name\":\"路人\",\"age\":30}";
          ????????String?encryptBody?=?EncryptionUtils.encrypt(body);
          ????????System.out.println(encryptBody);
          ????????String?desEncryptBody?=?EncryptionUtils.desEncrypt(encryptBody);
          ????????System.out.println(desEncryptBody);
          ????}
          ????private?static?void?m2(){
          ????????String?body?=?"[{\"name\":\"路人\",\"age\":30},{\"name\":\"springmvc高手系列\(zhòng)",\"age\":30}]";
          ????????String?encryptBody?=?EncryptionUtils.encrypt(body);
          ????????System.out.println(encryptBody);
          ????????String?desEncryptBody?=?EncryptionUtils.desEncrypt(encryptBody);
          ????????System.out.println(desEncryptBody);
          ????}

          ????private?static?String?AESTYPE?=?"AES/CBC/PKCS5Padding";

          ????/**
          ?????*?加密明文
          ?????*
          ?????*?@param?plainText?明文
          ?????*?@return
          ?????*?@throws?Exception
          ?????*/

          ????public?static?String?encrypt(String?plainText)?{
          ????????try?{
          ????????????Cipher?cipher?=?Cipher.getInstance(AESTYPE);
          ????????????byte[]?dataBytes?=?plainText.getBytes("utf-8");
          ????????????byte[]?plaintext?=?new?byte[dataBytes.length];
          ????????????System.arraycopy(dataBytes,?0,?plaintext,?0,?dataBytes.length);
          ????????????SecretKeySpec?keyspec?=?new?SecretKeySpec(key.getBytes(),?"AES");
          ????????????IvParameterSpec?ivspec?=?new?IvParameterSpec(key.getBytes());
          ????????????cipher.init(Cipher.ENCRYPT_MODE,?keyspec,?ivspec);
          ????????????byte[]?encrypted?=?cipher.doFinal(plaintext);
          ????????????return?new?String(Base64.getEncoder().encode(encrypted),?"UTF-8");
          ????????}?catch?(Exception?e)?{
          ????????????throw?new?RuntimeException(e);
          ????????}
          ????}

          ????/**
          ?????*?解密密文
          ?????*
          ?????*?@param?encryptData?密文
          ?????*?@return
          ?????*?@throws?Exception
          ?????*/

          ????public?static?String?desEncrypt(String?encryptData)?{
          ????????try?{
          ????????????Cipher?cipher?=?Cipher.getInstance(AESTYPE);
          ????????????SecretKeySpec?keyspec?=?new?SecretKeySpec(key.getBytes(),?"AES");
          ????????????IvParameterSpec?ivspec?=?new?IvParameterSpec(key.getBytes());
          ????????????cipher.init(Cipher.DECRYPT_MODE,?keyspec,?ivspec);
          ????????????byte[]?original?=?cipher.doFinal(Base64.getDecoder().decode(encryptData.getBytes("UTF-8")));
          ????????????return?new?String(original,?"utf-8");
          ????????}?catch?(Exception?e)?{
          ????????????throw?new?RuntimeException(e);
          ????????}

          ????}

          }

          3.5、驗證效果

          驗證效果

          接口明文密文
          /user/add{"name":"路人","age":30}0A10mig46aZI76jwpgmeeuqDHc7h4Zq/adoY6d5r2mY=
          /user/adds[{"name":"路人","age":30},{"name":"springmvc 高手系列","age":30}]UzWvCsrqt7ljXVI18XBXU3B9S4P2bMB72vH0HNst1GhMt5HTAiodbJwr7r8PuWWs1gM5iAYY4DZWfLgsTbizAEwEtqw8VuCuk2hYBjoCtCc=

          將項目發(fā)布到 tomcat,然后使用 idea 中的 HTTP client 跑下這 2 個測試用例

          POST?http://localhost:8080/chat13/user/add
          Content-Type:?application/json

          0A10mig46aZI76jwpgmeeuqDHc7h4Zq/adoY6d5r2mY=

          ###
          POST?http://localhost:8080/chat13/user/adds
          Content-Type:?application/json

          UzWvCsrqt7ljXVI18XBXU3B9S4P2bMB72vH0HNst1GhMt5HTAiodbJwr7r8PuWWs1gM5iAYY4DZWfLgsTbizAEwEtqw8VuCuk2hYBjoCtCc=

          輸出如下,變成明文了

          用例1輸出
          {
          ??"name":?"路人",
          ??"age":?30
          }

          用例2輸出
          [
          ??{
          ????"name":?"路人",
          ????"age":?30
          ??},
          ??{
          ????"name":?"springmvc高手系列",
          ????"age":?30
          ??}
          ]

          是不是特別的爽,無需在 controller 中進行解密,將解密統(tǒng)一放在RequestBodyAdvice中做了。

          4、多個 RequestBodyAdvice 指定順序

          當程序中定義了多個RequestBodyAdvice,可以通過下面 2 種方式來指定順序。

          方式 1:使用@org.springframework.core.annotation.Order注解指定順序,順序按照 value 的值從小到大,如:

          @Order(2)
          @ControllerAdvice
          public?class?RequestBodyAdvice1?extends?RequestBodyAdviceAdapter{}

          @Order(1)
          @ControllerAdvice
          public?class?RequestBodyAdvice2?extends?RequestBodyAdviceAdapter{}

          方式 1:實現(xiàn)org.springframework.core.Ordered接口,順序從小到大,如:

          @ControllerAdvice
          public?class?RequestBodyAdvice1?extends?RequestBodyAdviceAdapter?implements?Ordered{
          ?int?getOrder(){
          ????????return?1;
          ????}
          }

          @Order(1)
          @ControllerAdvice
          public?class?RequestBodyAdvice2?extends?RequestBodyAdviceAdapter?implements?Ordered{
          ?int?getOrder(){
          ????????return?2;
          ????}
          }

          5、@ControllerAdvice 指定增強的范圍

          @ControllerAdvice 注解相當于對 Controller 的功能進行了增強,目前來看,對所有的 controller 方法都增強了。

          那么,能否控制一下增強的范圍呢?比如對某些包中的 controller 進行增強,或者通過其他更細的條件來控制呢?

          確實可以,可以通過@ControllerAdvice 中的屬性來指定增強的范圍,需要滿足這些條件的才會被@ControllerAdvice 注解標注的 bean 增強,每個屬性都是數(shù)組類型的,所有的條件是或者的關系,滿足一個即可。

          @Target(ElementType.TYPE)
          @Retention(RetentionPolicy.RUNTIME)
          @Documented
          @Component
          public?@interface?ControllerAdvice?{

          ?/**
          ??*?用來指定controller所在的包,滿足一個就可以
          ??*/

          ?@AliasFor("basePackages")
          ?String[]?value()?default?{};

          ?/**
          ??*?用來指定controller所在的包,滿足一個就可以
          ??*/

          ?@AliasFor("value")
          ?String[]?basePackages()?default?{};

          ?/**
          ??*?controller所在的包必須為basePackageClasses中同等級或者子包中,滿足一個就可以
          ??*/

          ?Class[]?basePackageClasses()?default?{};

          ?/**
          ??*?用來指定Controller需要滿足的類型,滿足assignableTypes中指定的任意一個就可以
          ??*/

          ?Class[]?assignableTypes()?default?{};

          ?/**
          ??*?用來指定Controller上需要有的注解,滿足annotations中指定的任意一個就可以
          ??*/

          ?Class[]?annotations()?default?{};

          }

          擴展知識:這塊的判斷對應的源碼如下,有興趣的可以看看。

          org.springframework.web.method.HandlerTypePredicate#test

          6、RequestBodyAdvice 原理

          有些朋友可能對@ControllerAdvice和RequestBodyAdvice的原理比較感興趣,想研究一下他們的源碼,關鍵代碼在下面這個方法中,比較簡單,有興趣的可以去翻閱一下,這里就不展開說了。

          org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#afterPropertiesSet
          org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#initControllerAdviceCache

          7、總結

          • @ControllerAdvice 和 RequestBodyAdvice 一起使用可以攔截@RequestBody 標注的參數(shù),對參數(shù)進行增強處理
          • 建議:案例中 RequestBodyAdvice#supports 方法咱們直接返回的是 true,會對所有@RequestBody 標注的參數(shù)進行處理,有些參數(shù)可能不需要處理,對于這種情況的,supports 方法需要返回 false,這種問題留給大家自己試試了,挺簡單的,比如可以自定義一個注解標注在無需處理的參數(shù)上,檢測到參數(shù)上有這個注解的時候(supprts 方法中的 methodParameter 參數(shù)可以獲取參數(shù)的所有信息),supports 返回 false。

          8、留個問題

          若 body 中是 xml 格式的數(shù)據(jù),后端接口通過 java 對象接收,怎么實現(xiàn)呢?歡迎留言討論。

          有問題歡迎加我微信:itsoku,交流。

          9、SpringMVC 系列目錄

          1. SpringMVC 系列第 1 篇:helloword
          2. SpringMVC 系列第 2 篇:@Controller、@RequestMapping
          3. SpringMVC 系列第 3 篇:異常高效的一款接口測試利器
          4. SpringMVC 系列第 4 篇:controller 常見的接收參數(shù)的方式
          5. SpringMVC 系列第 5 篇:@RequestBody 大解密,說點你不知道的
          6. SpringMVC 系列第 6 篇:上傳文件的 4 種方式,你都會么?
          7. SpringMVC 系列第 7 篇:SpringMVC 返回視圖常見的 5 種方式,你會幾種?
          8. SpringMVC 系列第 8 篇:返回 json & 通用返回值設計
          9. SpringMVC 系列第 9 篇:SpringMVC 返回 null 是什么意思?
          10. SpringMVC 系列第 10 篇:異步處理
          11. SpringMVC 系列第 11 篇:集成靜態(tài)資源
          12. SpringMVC 系列第 12 篇:攔截器
          13. SpringMVC 系列第 13 篇:統(tǒng)一異常處理
          14. SpringMVC 系列第 14 篇:實戰(zhàn)篇:通用返回值 & 異常處理設計
          15. SpringMVC 系列第 15 篇:全注解的方式 ?&? 原理解析
          16. SpringMVC 系列第 16 篇:通過源碼解析 SpringMVC 處理請求的流程
          17. SpringMVC 系列第 17 篇:源碼解析 SpringMVC 容器的啟動過程

          10、更多好文章

          1. Spring 高手系列(共 56 篇)
          2. Java 高并發(fā)系列(共 34 篇)
          3. MySql 高手系列(共 27 篇)
          4. Maven 高手系列(共 10 篇)
          5. Mybatis 系列(共 12 篇)
          6. 聊聊 db 和緩存一致性常見的實現(xiàn)方式
          7. 接口冪等性這么重要,它是什么?怎么實現(xiàn)?
          8. 泛型,有點難度,會讓很多人懵逼,那是因為你沒有看這篇文章!

          11、【路人甲 Java】所有系列高清 PDF

          領取方式,掃碼發(fā)送:yyds

          瀏覽 144
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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毛片| 国产肥胖老太性HD | 九色偷拍 | 亚洲欧洲无码在线观看 | 五月婷婷五月 |