使用自定義HttpMessageConverter對(duì)返回內(nèi)容進(jìn)行加密

“如何在 Spring MVC 中統(tǒng)一對(duì)返回的 Json 進(jìn)行加密?”。

大部分人的第一反應(yīng)是通過(guò) Spring 攔截器(Interceptor)中的postHandler方法處理。實(shí)際這是行不通的,因?yàn)楫?dāng)程序運(yùn)行到該方法,是在返回?cái)?shù)據(jù)之后(controller執(zhí)行之后),渲染頁(yè)面之前,所以這時(shí)候 Response 中的對(duì)返回?cái)?shù)據(jù)的輸出已經(jīng)完成了,自然無(wú)法在對(duì)返回?cái)?shù)據(jù)進(jìn)行處理。

其實(shí)這個(gè)問(wèn)題用幾行代碼就可以搞定,因?yàn)?Spring 提供了非常豐富的擴(kuò)展支持,無(wú)論是之前提到的Interceptor和MethodArgumentResolver,還是接下來(lái)要提到的HttpMessageConverter。

在 Spring MVC 的 Controller 層經(jīng)常會(huì)用到@RequestBody和@ResponseBody,通過(guò)這兩個(gè)注解,可以在 Controller 中直接使用 Java 對(duì)象作為請(qǐng)求參數(shù)和返回內(nèi)容,而完成這之間轉(zhuǎn)換作用的便是HttpMessageConverter。

HttpMessageConverter接口提供了 5 個(gè)方法:

  • canRead
    :判斷該轉(zhuǎn)換器是否能將請(qǐng)求內(nèi)容轉(zhuǎn)換成 Java 對(duì)象
  • canWrite
    :判斷該轉(zhuǎn)換器是否可以將 Java 對(duì)象轉(zhuǎn)換成返回內(nèi)容
  • getSupportedMediaTypes
    :獲得該轉(zhuǎn)換器支持的 MediaType 類型
  • read
    :讀取請(qǐng)求內(nèi)容并轉(zhuǎn)換成 Java 對(duì)象
  • write
    :將 Java 對(duì)象轉(zhuǎn)換后寫入返回內(nèi)容

其中read和write方法的參數(shù)分別有有HttpInputMessage和HttpOutputMessage對(duì)象,這兩個(gè)對(duì)象分別代表著一次 Http 通訊中的請(qǐng)求和響應(yīng)部分,可以通過(guò)getBody方法獲得對(duì)應(yīng)的輸入流和輸出流。
這里通過(guò)實(shí)現(xiàn)該接口自定義一個(gè) Json 轉(zhuǎn)換器作為示例:

class CustomJsonHttpMessageConverter implements HttpMessageConverter {

    //Jackson 的 Json 映射類
    private ObjectMapper mapper = new ObjectMapper ();

    // 該轉(zhuǎn)換器的支持類型:application/json
    private List supportedMediaTypes = Arrays.asList (MediaType.APPLICATION_JSON);

    /**
    * 判斷轉(zhuǎn)換器是否可以將輸入內(nèi)容轉(zhuǎn)換成 Java 類型
    * @param clazz 需要轉(zhuǎn)換的 Java 類型
    * @param mediaType 該請(qǐng)求的 MediaType
    * @return
    */
    @Override
    public boolean canRead (Class clazz, MediaType mediaType) {
        if (mediaType == null) {
            return true;
        }
        for (MediaType supportedMediaType : getSupportedMediaTypes ()) {
            if (supportedMediaType.includes (mediaType)) {
                return true;
            }
        }
        return false;
    }

    /**
    * 判斷轉(zhuǎn)換器是否可以將 Java 類型轉(zhuǎn)換成指定輸出內(nèi)容
    * @param clazz 需要轉(zhuǎn)換的 Java 類型
    * @param mediaType 該請(qǐng)求的 MediaType
    * @return
    */
    @Override
    public boolean canWrite (Class clazz, MediaType mediaType) {
        if (mediaType == null || MediaType.ALL.equals (mediaType)) {
            return true;
        }
        for (MediaType supportedMediaType : getSupportedMediaTypes ()) {
            if (supportedMediaType.includes (mediaType)) {
                return true;
            }
        }
        return false;
    }

    /**
    * 獲得該轉(zhuǎn)換器支持的 MediaType
    * @return
    */
    @Override
    public List getSupportedMediaTypes () {
        return supportedMediaTypes;
    }

    /**
    * 讀取請(qǐng)求內(nèi)容,將其中的 Json 轉(zhuǎn)換成 Java 對(duì)象
    * @param clazz 需要轉(zhuǎn)換的 Java 類型
    * @param inputMessage 請(qǐng)求對(duì)象
    * @return
    * @throws IOException
    * @throws HttpMessageNotReadableException
    */
    @Override
    public Object read (Class clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
        return mapper.readValue (inputMessage.getBody (), clazz);
    }

    /**
    * 將 Java 對(duì)象轉(zhuǎn)換成 Json 返回內(nèi)容
    * @param o 需要轉(zhuǎn)換的對(duì)象
    * @param contentType 返回類型
    * @param outputMessage 回執(zhí)對(duì)象
    * @throws IOException
    * @throws HttpMessageNotWritableException
    */
    @Override
    public void write (Object o, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
        mapper.writeValue (outputMessage.getBody (), o);
    }
}

當(dāng)前 Spring 中已經(jīng)默認(rèn)提供了相當(dāng)多的轉(zhuǎn)換器,分別有:

名稱 作用 讀支持 MediaType 寫支持 MediaType
ByteArrayHttpMessageConverter 數(shù)據(jù)與字節(jié)數(shù)組的相互轉(zhuǎn)換 */* application/octet-stream
StringHttpMessageConverter 數(shù)據(jù)與 String 類型的相互轉(zhuǎn)換 text/* text/plain
FormHttpMessageConverter 表單與 MultiValueMap的相互轉(zhuǎn)換 application/x-www-form-urlencoded application/x-www-form-urlencoded
SourceHttpMessageConverter 數(shù)據(jù)與 javax.xml.transform.Source 的相互轉(zhuǎn)換 text/xml 和 application/xml text/xml 和 application/xml
MarshallingHttpMessageConverter 使用 Spring 的 Marshaller/Unmarshaller 轉(zhuǎn)換 XML 數(shù)據(jù) text/xml 和 application/xml text/xml 和 application/xml
MappingJackson2HttpMessageConverter 使用 Jackson 的 ObjectMapper 轉(zhuǎn)換 Json 數(shù)據(jù) application/json application/json
MappingJackson2XmlHttpMessageConverter 使用 Jackson 的 XmlMapper 轉(zhuǎn)換 XML 數(shù)據(jù) application/xml application/xml
BufferedImageHttpMessageConverter 數(shù)據(jù)與 java.awt.image.BufferedImage 的相互轉(zhuǎn)換 Java I/O API 支持的所有類型 Java I/O API 支持的所有類型

回到最開(kāi)始所提的需求,既然要對(duì)返回的 Json 內(nèi)容進(jìn)行加密,肯定是對(duì)MappingJackson2HttpMessageConverter
進(jìn)行改造,并且只需要重寫write方法。

從MappingJackson2HttpMessageConverter的父類AbstractHttpMessageConverter中的write方法可以看出,該方法通過(guò)writeInternal方法向返回結(jié)果的輸出流中寫入數(shù)據(jù),所以只需要重寫該方法即可:

@Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter () {
    return new MappingJackson2HttpMessageConverter () {
        // 重寫 writeInternal 方法,在返回內(nèi)容前首先進(jìn)行加密
        @Override
        protected void writeInternal (Object object, HttpOutputMessage outputMessage) throws IOException,
        HttpMessageNotWritableException {
            // 使用 Jackson 的 ObjectMapper 將 Java 對(duì)象轉(zhuǎn)換成 Json String
            ObjectMapper mapper = new ObjectMapper ();
            String json = mapper.writeValueAsString (object);
            LOGGER.error (json);
            // 加密
            String result = json + "加密了!";
            LOGGER.error (result);
            // 輸出
            outputMessage.getBody ().write (result.getBytes ());
        }
    };
}

在這之后還需要將這個(gè)自定義的轉(zhuǎn)換器配置到 Spring 中,這里通過(guò)重寫WebMvcConfigurer
中的configureMessageConverters
方法添加自定義轉(zhuǎn)換器:

// 添加自定義轉(zhuǎn)換器
@Override
public void configureMessageConverters (List> converters) {
    converters.add (mappingJackson2HttpMessageConverter ());
    super.configureMessageConverters (converters);
}

測(cè)試一下:

如此便簡(jiǎn)單的完成了對(duì)返回內(nèi)容進(jìn)行加密的功能。(一般加密也伴隨入?yún)⒌慕饷堋K院芏鄨?chǎng)景下read方法也需解密,不過(guò)思路一致)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,923評(píng)論 18 139
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法,類相關(guān)的語(yǔ)法,內(nèi)部類的語(yǔ)法,繼承相關(guān)的語(yǔ)法,異常的語(yǔ)法,線程的語(yǔ)...
    子非魚(yú)_t_閱讀 31,765評(píng)論 18 399
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,951評(píng)論 6 342
  • 我一直都記得,一次薩提亞課堂開(kāi)課前的自我介紹,當(dāng)有一個(gè)同學(xué)說(shuō),我剛離婚,心情很低落,想來(lái)到課堂上尋求一些力量…… ...
    葉子1975閱讀 400評(píng)論 0 0
  • 手里有個(gè)項(xiàng)目,要在win環(huán)境下連接linux,用到了xshell5.0。開(kāi)始用的時(shí)候沒(méi)問(wèn)題,昨天再打開(kāi)xshell...
    我是王小一閱讀 1,968評(píng)論 0 1