Undertow (JBoss 7) 在重定向时破坏/重新编码 URL 编码的参数

Posted

技术标签:

【中文标题】Undertow (JBoss 7) 在重定向时破坏/重新编码 URL 编码的参数【英文标题】:Undertow (JBoss 7) mangles / re-encodes URL-encoded parameter on redirect 【发布时间】:2019-10-23 07:39:57 【问题描述】:

也发布在 JBoss 论坛中: https://developer.jboss.org/thread/280195

更新 2019-06-26 显然这现在已被确认为 Undertow 中的一个错误,并提交了拉取请求 here。

这是一个 SSCCE。

我有一个非常简单的 Servlet,除了打印参数值之外什么都不做:

public class TestServlet extends HttpServlet
    public void service(HttpServletRequest req, HttpServletResponse res)
    throws ServletException, IOException 
        final String URL = req.getParameter("url");
        System.out.printf("url parameter read as: [%s]\n", URL);
    

我的应用程序的 web.xml 配置为自动将http 访问重定向到https

<web-app>
 ...
<security-constraint>
    <web-resource-collection>
        <web-resource-name>SECURE</web-resource-name>
        <url-pattern>/*</url-pattern>
    </web-resource-collection>
    <user-data-constraint>
        <transport-guarantee>CONFIDENTIAL</transport-guarantee>
    </user-data-constraint>
</security-constraint>
</web-app>

…而且我还(在我的standalone-full.xml 配置文件中)在http-listener 的定义中设置了redirect-socket 属性:

<http-listener name="default" socket-binding="http" redirect-socket="https" enable-http2="true"/>

如果我部署到 JBoss EAP 7.1 并在我的浏览器中输入以下 URL(其中url 参数带有“http://www.google.com”的 URL 编码值):

http://localhost:8082/get-parameter-test/min?url=http%3A%2F%2Fwww.google.com

…这是我在开发者控制台中看到的:

结果,自动重定向后,我的代码无法获取url参数的正确值,我在日志文件中看到:

url parameter read as: [http%3A%2F%2Fwww.google.com]

但是,如果我部署到 JBoss EAP 6.2 并执行相同的操作,则 URL 不会在重定向中被破坏并且一切正常:

更新

this answer暗示JBoss配置文件(standalong-full.xml)的undertow系统中http-listenerhttps-listener的配置中的decode-url参数可能与此有关。这是错误的。我尝试了所有四种组合:

http-listener:decode-url="false" 和 https-listener:decode-url="false" http-listener:decode-url="false" 和 https-listener:decode-url="true" http-listener:decode-url="true" 和 https-listener:decode-url="false" http-listener:decode-url="true" 和 https-listener:decode-url="true"

在所有情况下,影响从 http 到 https 的重定向的 302 响应具有以下标头:

Location: https://localhost:8445/get-parameter-test?url=http%253A%252F%252Fwww.google.com

也就是说,在所有情况下,URL 都是错误的(如果您愿意,可以将其称为重新编码,它是错误的 AFAIAC)。这种行为根本没有理由,也不是 EAP 6.2 所做的。 decode-url 参数的值只影响 servlet 中 HttpServletRequest#getRequest 方法的行为,它对重定向的 URL 没有任何影响。

【问题讨论】:

【参考方案1】:

更新 2019-06-26 显然,现在这已被确认为 Undertow,提交了一个拉取请求here

这就是最终对我有用的方法。 首先,我从我的web.xml 中删除了整个&lt;security-constraint&gt; 元素,因为我实施的解决方案不需要它。 我还从&lt;http-listener&gt; 配置中删除了redirect-socket="https"。这也是不需要的。这就是我的&lt;http-listener&gt;&lt;https-listener&gt; 的样子:

<http-listener name="default" socket-binding="http" enable-http2="true"/>
<https-listener name="https" socket-binding="https" security-realm="ApplicationRealm" enable-http2="true"/>

我认为以上内容正是您在 JBoss EAP 7.1 中开箱即用的内容,因此无需更改。

然后我创建了一个过滤器并将其添加到 undertow 子系统的 &lt;filters&gt; 元素中:

<rewrite name="http-to-https" redirect="true" target="https://%h:8445%U%q"/>
%h 是远程主机名 %U 是请求的 URL 路径 %q 是查询字符串(如果存在则自动添加?

我找到了上述代码 here - 我确信在其他地方有更规范的参考,但它们似乎有效。

最后,我在&lt;server&gt;/&lt;host&gt; 元素(也在undertow 子系统中)添加了对过滤器的引用以及谓词:

<server name="default-server">
    <http-listener name="default" socket-binding="http" enable-http2="true"/>
    <https-listener name="https" socket-binding="https" security-realm="ApplicationRealm" enable-http2="true"/>
    <host name="default-host" alias="localhost">
        <location name="/" handler="welcome-content"/>
        <filter-ref name="server-header"/>
        <filter-ref name="x-powered-by-header"/>
        <filter-ref name="http-to-https" predicate="equals(%p, 8082)"/>
        <http-invoker security-realm="ApplicationRealm"/>
    </host>
</server>

通过上述配置,请求会被重定向,而无需重新编码 URL:

$ curl -I -L -k http://localhost:8082/get-parameter-test?url=http%3A%2F%2Fwww.google.com
HTTP/1.1 302 Found
Connection: keep-alive
Server: JBoss-EAP/7
Location: https://127.0.0.1:8445/get-parameter-test?url=http%3A%2F%2Fwww.google.com
Content-Length: 0
Date: Tue, 11 Jun 2019 17:43:23 GMT

HTTP/1.1 200 OK
Connection: keep-alive
X-Powered-By: Undertow/1
Server: JBoss-EAP/7
Content-Length: 0
Date: Tue, 11 Jun 2019 17:43:23 GMT

… 并且从 Java 中正确读取了参数:

url parameter read as: [http://www.google.com]

不需要在 http/https 监听器中设置decode-url="true",因为这是默认值。

注意:以上导致 JBoss EAP 7.1 发送 302 重定向。我不知道如何配置 303 或 307 重定向。

结束语

上述的明显替代方法是使用HttpServletRequest#sendRedirect 以编程方式从您的应用程序代码执行重定向。在这种情况下,你确实不需要在你的http-listener 中需要redirect-socket="https"

显然,redirect-socket 属性只需要与应用程序 web.xml 中的 &lt;security-constraint&gt; 元素结合使用。那是因为否则(例如,如果您的 web.xml 中有 &lt;security-constraint&gt;,但您的 http-listener 中没有 redirect-socket),您会受到以下影响:

ERROR [io.undertow.request] (default task-14) UT005001: An exception occurred processing the request: java.lang.IllegalStateException: UT010053: No confidential port is available to redirect the current request.)。

但是,如果您同时拥有&lt;security-constraint&gt;redirect-socket,则查询字符串在重定向的 URL 中会不必要地重新编码(并因此被破坏),如问题中所述。所以我不清楚在 JBoss EAP 7.1 中&lt;security-constraint&gt; 的用途是什么。

【讨论】:

【参考方案2】:

您的 URL 没有被重新编码。问题是从 http 重定向到 https 时重新编码或 URL。您正在将编码的 url 参数传递给 http,即 http%3A%2F%2Fwww.google.com。

http://www.google.com --encode--> http%3A%2F%2Fwww.google.com --re-encode--> http%253A%252F%252Fwww.google.com

在您重新编码 url 时,'%' 被编码为 '%25'。

要禁用此行为,您需要在侦听器中进行更改。有一个名为 decode-url 的属性可用于禁用/启用此行为。

decode-url : 如果 URL 应该被解码。如果未设置为 如果为 true,则 URL 中的百分比编码字符将保持原样。

有关 Undertow 子系统的更多信息,请参阅以下链接。 https://docs.jboss.org/author/display/WFLY/Undertow+subsystem+configuration

注意:这个功能可能在 JBoss 6.2 上有效,因为在 JBoss 6.2 中这个配置的子系统称为 WEB,在 JBoss EAP 7+ 中被 Undertow 取代

【讨论】:

以上是关于Undertow (JBoss 7) 在重定向时破坏/重新编码 URL 编码的参数的主要内容,如果未能解决你的问题,请参考以下文章

如何为 Undertow / JBoss 7.2 EAP 重写 RewriteValve?

在 JBOSS EAP 7.1 中,如何将 EJB 和 undertow 的统计启用设置为 True?

Wildfly Undertow 中的 HTTPS 重定向

如何在 Undertow/EAP 7 上提供过滤器参数?

Keycloak/Undertow/JBoss-CLI - 从环境变量中设置“web-context”

Jboss7:Undertow Spring Boot 抛出 404