在 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 上獲取。