在 Java 中的 HttpServletRequest 中設置參數
1. 概述
當使用 Servlet API 用 Java 開發 Web 應用程序時, HttpServletRequest
對像在處理傳入的 HTTP 請求中起著關鍵作用。它提供對請求各個方面的訪問,例如參數、標頭和屬性。
請求參數始終由 HTTP 客戶端提供。但是,在某些情況下,我們可能需要在應用程序處理HttpServletRequest
對象之前以編程方式設置參數。
需要注意的是, HttpServletRequest
缺少用於添加新參數或更改參數值的 setter 方法。在本文中,我們將探討如何通過擴展原始HttpServletRequest
的功能來實現這一目標。
2.Maven依賴
除了標準Java Servlet API 之外:
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
我們還將在一個用例中使用commons-text庫,通過轉義 HTML 實體來清理請求參數:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>1.10.0</version>
</dependency>
3. 基本 Servlet 組件
在深入研究實際使用示例之前,讓我們快速瀏覽一下我們將使用的一些基本 Servlet 組件。
3.1. HttpServletRequest
HttpServletRequest
類是客戶端和 Servlet 之間通信的主要方式。它封裝傳入的 HTTP 請求,提供對參數、標頭和其他請求相關信息的訪問。
3.2. HttpServletRequestWrapper
HttpServletRequestWrapper
通過充當現有HttpServletRequest
對象的裝飾器來擴展HttpServletRequest
的功能。這使我們能夠根據我們的具體需求附加額外的責任。
3.3. Filter
Filter
在請求和響應遍歷 servlet 容器時捕獲並處理請求和響應。這些Filters
被設計為在 Servlet 執行之前調用,使它們能夠更改傳入請求和傳出響應。
4. 參數清理
在HttpServletRequest
中以編程方式設置參數的應用之一是清理請求參數,從而有效減少跨站腳本 (XSS) 漏洞。此過程涉及消除或編碼用戶輸入中的潛在有害字符,從而增強 Web 應用程序的安全性。
4.1.例子
現在,讓我們詳細探討一下該過程。首先,我們要設置一個Servlet Filter
來攔截請求。過濾器提供了一種在請求和響應到達目標 Servlet 或 JSP 之前對其進行修改的方法。
下面是 Servlet Filter
的具體示例,它攔截對特定 URL 模式的所有請求,確保過濾器鏈通過原始HttpSevletRequest
對象返回SanitizeParametersRequestWrapper
:
@WebFilter(urlPatterns = {"/sanitize/with-sanitize.jsp"})
public class SanitizeParametersFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpSerlvetRequest httpReq = (HttpSerlvetRequest) request;
chain.doFilter(new SanitizeParametersRequestWrapper(httpReq), response);
}
}
SanitizeParameterRequestWrapper
類擴展了HttpServletRequestWrapper
。這在參數清理過程中起著至關重要的作用。此類旨在清理原始HttpServletRequest
中的請求參數,並僅將清理後的參數公開給調用 JSP:
public class SanitizeParametersRequestWrapper extends HttpServletRequestWrapper {
private final Map<String, String[]> sanitizedMap;
public SanitizeParametersRequestWrapper(HttpServletRequest request) {
super(request);
sanitizedMap = Collections.unmodifiableMap(
request.getParameterMap().entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
entry -> Arrays.stream(entry.getValue())
.map(StringEscapeUtils::escapeHtml4)
.toArray(String[]::new)
)));
}
@Override
public Map<String, String[]> getParameterMap() {
return sanitizedMap;
}
@Override
public String[] getParameterValues(String name) {
return Optional.ofNullable(getParameterMap().get(name))
.map(values -> Arrays.copyOf(values, values.length))
.orElse(null);
}
@Override
public String getParameter(String name) {
return Optional.ofNullable(getParameterValues(name))
.map(values -> values[0])
.orElse(null);
}
}
在構造函數中,我們迭代每個請求參數並使用StringEscapeUtils.escapeHtml4
來清理該值。處理後的參數被收集在新的經過淨化的地圖中。
我們重寫getParameter
方法,從清理後的映射中返回相應的參數,而不是原始的請求參數。儘管getParameter
方法是使用的主要焦點,但重寫getParameterMap
和getParameterValues
也很重要。我們確保所有參數檢索方法之間行為一致,並在整個過程中維護安全標準。
根據規範, getParameterMap
方法保證不可修改的映射,以防止更改內部值。因此,遵守此契約並確保覆蓋也返回不可修改的映射非常重要。同樣,重寫的getParameterValues
方法返回克隆數組而不是其內部值。
現在,讓我們創建一個 JSP 來說明我們的工作。它只是將input
請求參數的值呈現在屏幕上:
The text below comes from request parameter "input":<br/>
<%=request.getParameter("input")%>
4.2.結果
現在,讓我們在沒有激活清理過濾器的情況下運行 JSP。我們將腳本標記作為反射 XSS 注入到請求參數中:
http://localhost:8080/sanitize/without-sanitize.jsp?input=<script>alert('Hello');</script>
我們將看到瀏覽器正在執行參數中嵌入的 JavaScript:
接下來,我們將運行清理後的版本:
http://localhost:8080/sanitize/with-sanitize.jsp?input=<script>alert('Hello');</script>
在這種情況下,過濾器將捕獲請求並將SanitizeParameterRequestWrapper
傳遞給調用 JSP。結果,彈出窗口將不再出現。相反,我們將觀察到 HTML 實體被轉義並在屏幕上可見地呈現:
The text below comes from request parameter "input":
<script>alert('Hello');</script>
5. 第三方資源訪問
讓我們考慮另一種場景,其中第三方模塊接受請求參數locale
設置以更改模塊顯示的語言。請注意,我們無法直接修改第三方模塊的源代碼。
在我們的示例中,我們將locale
參數設置為默認系統區域設置。但是,它也可以從其他來源獲取,例如 HTTP 會話。
5.1.例子
第三方模塊是一個JSP頁面:
<%
String localeStr = request.getParameter("locale");
Locale currentLocale = (localeStr != null ? new Locale(localeStr) : null);
%>
The language you have selected: <%=currentLocale != null ? currentLocale.getDisplayLanguage(currentLocale) : " None"%>
如果提供了locale
參數,模塊將顯示語言名稱。
我們將為目標請求參數提供默認系統語言Locale.getDefault().getLanguage()
。為此,我們在SetParameterRequestWrapper
中設置locale
設置參數,該參數裝飾原始的HttpServletRequest
:
@WebServlet(name = "LanguageServlet", urlPatterns = "/setparam/lang")
public class LanguageServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
SetParameterRequestWrapper requestWrapper = new SetParameterRequestWrapper(request);
requestWrapper.setParameter("locale", Locale.getDefault().getLanguage());
request.getRequestDispatcher("/setparam/3rd_party_module.jsp").forward(requestWrapper, response);
}
}
創建SetParameterRequestWrapper
時,我們將採用與上一節類似的方法。此外,我們將實現setParameter
方法以將新參數添加到現有參數映射中:
public class SetParameterRequestWrapper extends HttpServletRequestWrapper {
private final Map<String, String[]> paramMap;
public SetParameterRequestWrapper(HttpServletRequest request) {
super(request);
paramMap = new HashMap<>(request.getParameterMap());
}
@Override
public Map<String, String[]> getParameterMap() {
return Collections.unmodifiableMap(paramMap);
}
public void setParameter(String name, String value) {
paramMap.put(name, new String[] {value});
}
// getParameter() and getParameterValues() are the same as SanitizeParametersRequestWrapper
}
getParameter
方法檢索存儲在paramMap
中的參數,而不是原始請求。
5.2.結果
LangaugeServlet
通過SetParameterRequestWrapper
將語言環境參數傳遞給第三方模塊。當我們在英語服務器上訪問第三方模塊時,我們將看到以下內容:
The language you have selected: English
六,結論
在本文中,我們了解了一些重要的 Servlet 組件,可以在 Java Web 應用程序開發中使用它們來處理客戶端請求。其中包括HttpServletRequest
、 HttpServletRequestWrapper
和Filter
。
通過具體示例,我們演示瞭如何擴展HttpServletRequest
的功能,以編程方式設置和修改請求參數。
無論是通過清理用戶輸入來增強安全性,還是與第三方資源集成,這些技術都使開發人員能夠應對不同的場景。
與往常一樣,此示例的完整源代碼可在 GitHub 上獲取。