Set a Parameter in an HttpServletRequest in Java – 在 Java 的 HttpServletRequest 中设置参数

最后修改: 2023年 9月 5日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

When developing web applications in Java using the Servlet API, the HttpServletRequest object plays a key role in processing incoming HTTP requests. It provides access to various aspects of the request such as parameters, headers, and attributes.

当使用 Servlet API 在 Java 中开发 Web 应用程序时,HttpServletRequest 对象在处理传入的 HTTP 请求中起着关键作用。它提供了对请求各方面(如参数、头和属性)的访问。

Request parameters are always supplied by the HTTP client. However, there are scenarios where we might need to set parameters programmatically within the HttpServletRequest object before it’s processed by our application.

请求参数总是由 HTTP 客户端提供。不过,在某些情况下,我们可能需要在应用程序处理 HttpServletRequest 对象之前,以编程方式在其中设置参数。

It’s important to note that HttpServletRequest lacks setter methods for adding new parameters or altering parameter values. In this article, we’ll explore how to achieve this by extending the functionalities of the original HttpServletRequest.

值得注意的是,HttpServletRequest 缺少用于添加新参数或更改参数值的 setter 方法。在本文中,我们将探讨如何通过扩展原始 HttpServletRequest 的功能来实现这一点。

2. Maven Dependencies

2.Maven 依赖项

In addition to the standard Java Servlet API:

除了标准的 Java Servlet API 之外:

<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>4.0.1</version>
    <scope>provided</scope>
</dependency>

We’ll also use the commons-text library in one of our use cases to sanitize request parameters by escaping HTML entities:

我们还将在其中一个用例中使用 commons-text 库,通过转义 HTML 实体对请求参数进行消毒:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-text</artifactId>
    <version>1.10.0</version>
</dependency>

3. Essential Servlet Components

3.基本 Servlet 组件

Before we dive into the real-world usage examples, let’s take a quick look at some foundational Servlet components that we’ll make use of.

在深入了解实际使用示例之前,让我们先快速了解一下我们将使用的一些基础 Servlet 组件。

3.1. HttpServletRequest

3.1 HttpServletRequest

The HttpServletRequest class is the primary means of communication between clients and Servlets. It encapsulates incoming HTTP requests, providing access to parameters, headers, and other request-related information.

HttpServletRequest 类是客户端与 Servlet 之间的主要通信方式。它封装传入的 HTTP 请求,提供对参数、头和其他请求相关信息的访问。

3.2. HttpServletRequestWrapper

3.2 HttpServletRequestWrapper

The HttpServletRequestWrapper extends the capabilities of HttpServletRequest by acting as a decorator of an existing HttpServletRequest object. This allows us to attach additional responsibilities according to our specific needs.

HttpServletRequestWrapper 通过充当现有 HttpServletRequest 对象的 decorator 来扩展 HttpServletRequest 的功能。这样,我们就可以根据具体需要附加额外的责任

3.3. Filter

3.3 过滤器</em

A Filter captures and processes requests and responses as they traverse the servlet container. These Filters are designed to invoke before Servlet execution, empowering them to alter both incoming requests and outgoing responses.

Filter 可在请求和响应穿越 Servlet 容器时对其进行捕获和处理。这些过滤器被设计为在 Servlet 执行之前调用,使它们能够改变传入的请求和传出的响应

4. Parameter Sanitization

4.参数消毒

One of the applications of setting parameters programmatically within HttpServletRequest is to sanitize request parameters, effectively mitigating Cross-Site Scripting (XSS) vulnerabilities. This procedure involves the elimination or encoding of potentially harmful characters from user input, thus enhancing the security of the web application.

HttpServletRequest 中以编程方式设置参数的应用之一是对请求参数进行消毒,从而有效缓解 跨站脚本 (XSS) 漏洞。该过程涉及消除用户输入中可能有害的字符或对其进行编码,从而增强网络应用程序的安全性。

4.1. Example

4.1.示例

Now, let’s explore the process in detail. First of all, we have to set up a Servlet Filter to intercept the requests. The filter provides a way to modify the request and response before it reaches the target Servlet or JSP.

现在,让我们来详细探讨一下这个过程。首先,我们必须设置一个 Servlet Filter 来拦截请求。过滤器提供了一种在请求和响应到达目标 Servlet 或 JSP 之前对其进行修改的方法。

Here’s a concrete example of a Servlet Filter that intercepts all requests to a specific URL pattern, ensuring the filter chain returns the SanitizeParametersRequestWrapper over the original HttpSevletRequest object:

下面是一个 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);
    }
}

The class SanitizeParameterRequestWrapper extends HttpServletRequestWrapper. This plays a crucial role in the parameter sanitization process. This class is designed to sanitize the request parameters from the original HttpServletRequest and expose only the sanitized parameters to the calling JSP:

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);
    }

}

In the constructor, we iterate over each request parameter and use StringEscapeUtils.escapeHtml4 to sanitize the value. The processed parameters are collected in a new sanitized map.

在构造函数中,我们遍历每个请求参数,并使用 StringEscapeUtils.escapeHtml4 对值进行消毒。处理过的参数会被收集到一个新的经过消毒的映射中。

We override the getParameter method to return the corresponding parameter from the sanitized map instead of the original request parameters. Even though the getParameter method is the primary focus of use, it’s vital to override getParameterMap and getParameterValues, too. We ensure consistent behavior among all parameter retrieval methods and maintain security standards throughout them.

我们重载 getParameter 方法,以从经过净化的映射中返回相应的参数,而不是原始请求参数。尽管 getParameter 方法是使用的主要重点,但覆盖 getParameterMapgetParameterValues 也至关重要。我们确保所有参数检索方法的行为一致,并在整个方法中维护安全标准。

As per the specification, the getParameterMap method guarantees an unmodifiable map that prevents changing the internal values. Hence, it’s important to adhere to this contract and to ensure that the override also returns an unmodifiable map. Similarly, the overridden getParameterValues method returns a cloned array instead of its internal value.

根据规范,getParameterMap 方法保证返回不可修改的映射,以防止更改内部值。因此,遵守此契约并确保重载也返回不可修改的映射非常重要。同样,重载 getParameterValues 方法将返回一个克隆数组,而不是其内部值。

Now, let’s create a JSP to illustrate our work. It simply renders the value of the input request parameter on the screen:

现在,让我们创建一个 JSP 来说明我们的工作。它只需在屏幕上显示 input 请求参数的值:

The text below comes from request parameter "input":<br/>
<%=request.getParameter("input")%>

4.2. Results

4.2.结果

Now, let’s run the JSP without the sanitization filter activated. We’ll inject a script tag to the request parameter as a reflected XSS:

现在,让我们在未激活消毒过滤器的情况下运行 JSP。我们将向请求参数注入一个脚本标记,作为反射 XSS:

http://localhost:8080/sanitize/without-sanitize.jsp?input=<script>alert('Hello');</script>

We’ll see that the JavaScript embedded in the parameter is being executed by the browser:

我们将看到浏览器正在执行嵌入在参数中的 JavaScript:

hello popup

hello popup

Next, we’ll run the sanitized version:

接下来,我们运行经过消毒的版本:

http://localhost:8080/sanitize/with-sanitize.jsp?input=<script>alert('Hello');</script>

In this case, the filter will capture the request and pass the SanitizeParameterRequestWrapper to the calling JSP. As a result, the popup will no longer appear. Instead, we’ll observe that HTML entities are escaped and visibly rendered on the screen:

在这种情况下,过滤器将捕获请求并将 SanitizeParameterRequestWrapper 传递给调用的 JSP。因此,弹出窗口将不再出现。取而代之的是,我们将观察到 HTML 实体被转义并明显地呈现在屏幕上:

The text below comes from request parameter "input":
<script>alert('Hello');</script>

5. Third-Party Resource Access

5.第三方资源访问

Let’s consider another scenario where a third-party module accepts a request parameter locale to change the language displayed by the module. Note that we are unable to modify the source code of the third-party module directly.

让我们考虑另一种情况:第三方模块接受一个请求参数 locale,以更改模块显示的语言。请注意,我们无法直接修改第三方模块的源代码。

In our example, we’ll set the locale parameter to the default system locale. However, it could also be taken from another source, such as the HTTP session.

在我们的示例中,我们将把 locale 参数设置为默认系统语言。不过,它也可以来自其他来源,例如 HTTP 会话。

5.1. Example

5.1.示例

The third-party module is a JSP page:

第三方模块是一个 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"%>

If the locale parameter is provided, the module displays the language name.

如果提供了 locale 参数,模块将显示语言名称。

We’ll provide the default system language Locale.getDefault().getLanguage() to the target request parameter. To do this, let’s set the locale parameter in our SetParameterRequestWrapper, which decorates the original HttpServletRequest:

我们将为目标请求参数提供默认系统语言 Locale.getDefault().getLanguage() 。为此,我们将在装饰原始 HttpServletRequestSetParameterRequestWrapper 中设置 locale 参数:

@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);
    }
}

We’ll take a similar approach to the previous section when creating the SetParameterRequestWrapper. In addition, we’ll implement the setParameter method to add a new parameter to the existing parameter map:

在创建 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
}

The getParameter method retrieves parameters stored within the paramMap instead of the original request.

getParameter 方法将检索存储在 paramMap 中的参数,而不是原始请求。

5.2. Results

5.2.结果

LangaugeServlet passes the locale parameter to the third-party module via SetParameterRequestWrapper. We’ll see the following when we access the third-party module on an English language server:

LangaugeServlet 通过 SetParameterRequestWrapper 将 locale 参数传递给第三方模块。当我们在英语服务器上访问第三方模块时,我们将看到以下内容:

The language you have selected: English

6. Conclusion

6.结论

In this article, we looked at some important Servlet components that we can use in Java web application development to handle client requests. These include HttpServletRequest, HttpServletRequestWrapper, and Filter.

在本文中,我们介绍了一些重要的 Servlet 组件,我们可以在 Java Web 应用程序开发中使用这些组件来处理客户端请求。这些组件包括 HttpServletRequestHttpServletRequestWrapperFilter

Through concrete examples, we’ve demonstrated how to extend the capabilities of HttpServletRequest to programmatically set and modify request parameters.

通过具体示例,我们演示了如何扩展 HttpServletRequest 的功能,以编程方式设置和修改请求参数。

Whether it’s for enhancing security by sanitizing user input or integrating with third-party resources, these techniques empower developers to cope with diverse scenarios.

无论是通过对用户输入进行消毒来增强安全性,还是与第三方资源进行整合,这些技术都能让开发人员应对各种不同的场景。

As usual, the full source code for this example is available over on GitHub.

与往常一样,本示例的完整源代码可在 GitHub 上获取。