Spring Boot Filter中擷取響應輸出內容

banq發表於2024-04-14

在本文中,我們將探討如何從Spring Boot 過濾器中的ServletResponse檢索響應正文。

本質上,我們將定義問題,然後使用快取響應正文的解決方案,使其在 Spring Boot 過濾器中可用。讓我們開始。

首先,讓我們瞭解我們要解決的問題。

使用 Spring Boot 過濾器時,從 ServletResponse 訪問響應主體很 棘手。這是因為響應主體不容易獲得,因為它是在過濾器鏈完成執行後寫入輸出流的。

但是,某些操作(例如生成雜湊簽名)需要響應正文的內容,然後才能將其傳送到客戶端。因此,我們需要找到一種方法來讀取body的內容。

在過濾器中使用ContentCachingResponseWrapper
為了克服前面定義的問題,我們將建立一個自定義過濾器並使用Spring Framework 提供的ContentCachingResponseWrapper類:

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) 
  throws IOException, ServletException {
    ContentCachingResponseWrapper responseCacheWrapperObject = 
      new ContentCachingResponseWrapper((HttpServletResponse) servletResponse);
    filterChain.doFilter(servletRequest, responseCacheWrapperObject);
    byte[] responseBody = responseCacheWrapperObject.getContentAsByteArray();
    MessageDigest md5Digest = MessageDigest.getInstance(<font>"MD5");
    byte[] md5Hash = md5Digest.digest(responseBody);
    String md5HashString = DatatypeConverter.printHexBinary(md5Hash);
    responseCacheWrapperObject.getResponse().setHeader(
"Response-Body-MD5", md5HashString);
   
// ...<i>
}

簡而言之,包裝類允許我們包裝HttpServletResponse來快取響應正文內容,並呼叫doFilter()將請求傳遞到下一個過濾器。

請記住,我們一定不要忘記這裡的doFilter()呼叫。否則,傳入的請求將不會進入 Spring 過濾器鏈中的下一個過濾器,應用程式也不會按照我們的預期處理該請求。事實上,不呼叫doFilter()是違反servlet規範的。

此外,我們一定不要忘記使用responseCacheWrapperObject呼叫doFilter ()。否則,響應正文將不會被快取。簡而言之,ContentCachingResponseWrapper將過濾器放置在響應輸出流和發出 HTTP 請求的客戶端之間。因此,在建立響應主體輸出流後(在本例中是在doFilter()呼叫之後),內容可在過濾器內進行處理。

使用包裝器後,可以使用getContentAsByteArray()方法在過濾器中獲取響應正文。我們使用這個方法來計算MD5雜湊值。

首先,我們使用MessageDigest類建立響應正文的 MD5 雜湊值。其次,我們將位元組陣列轉換為十六進位制字串。第三,我們使用setHeader()方法將生成的雜湊字串設定為響應物件的標頭。

如果需要,我們可以將位元組陣列轉換為字串,並使正文的內容更加明確。

最後,在退出doFilter()方法之前呼叫copyBodyToResponse()至關重要,以將更新後的響應正文複製回原始響應:

responseCacheWrapperObject.copyBodyToResponse();

在退出doFilter()方法之前呼叫copyBodyToResponse()至關重要。否則,客戶端將不會收到完整的響應。

配置過濾器
現在,我們需要在 Spring Boot 中新增過濾器:

@Bean
public FilterRegistrationBean loggingFilter() {
    FilterRegistrationBean registrationBean = new FilterRegistrationBean<>();
    registrationBean.setFilter(new MD5Filter());
    return registrationBean;
}

在這裡,我們使用 我們之前建立的過濾器的實現來配置建立一個FilterRegistrationBean 。

測試MD5
最後,我們可以使用Spring 中的整合測試來測試一切是否按預期工作:

@Test
void whenExampleApiCallThenResponseHasMd5Header() throws Exception {
    String endpoint = <font>"/api/example";
    String expectedResponse =
"Hello, World!";
    String expectedMD5 = getMD5Hash(expectedResponse);
    MvcResult mvcResult = mockMvc.perform(get(endpoint).accept(MediaType.TEXT_PLAIN_VALUE))
      .andExpect(status().isOk())
      .andReturn();
    String md5Header = mvcResult.getResponse()
      .getHeader(
"Response-Body-MD5");
    assertThat(md5Header).isEqualTo(expectedMD5);
}

在這裡,我們呼叫/api/example控制器,它返回“Hello, World!”正文中的文字。我們定義了getMD5Hash()方法,它將響應轉換為 MD5,類似於我們在過濾器中使用的:

private String getMD5Hash(String input) throws NoSuchAlgorithmException {
    MessageDigest md5Digest = MessageDigest.getInstance(<font>"MD5");
    byte[] md5Hash = md5Digest.digest(input.getBytes(StandardCharsets.UTF_8));
    return DatatypeConverter.printHexBinary(md5Hash);
}

結論
在本文中,我們學習瞭如何使用ContentCachingResponseWrapper類從Spring Boot 過濾器中的ServletResponse檢索響應正文。我們使用此機制來展示如何在 HTTP 響應標頭中實現正文的 MD5 編碼。

相關文章