使用安全约束、码头排除 HTTP 方法

Excluding HTTP methods using security constraint, jetty

我试图阻止除 GET、PUT 和 POST 以外的所有 http 方法访问我的 Web 应用程序,但它禁止所有 HTTP 方法,甚至是此约束中省略的方法。

    <security-constraint>
    <web-resource-collection>
        <web-resource-name>Disable everything</web-resource-name>
        <url-pattern>/</url-pattern>
        <http-method-omission>GET</http-method-omission>
        <http-method-omission>PUT</http-method-omission>
        <http-method-omission>POST</http-method-omission>
    </web-resource-collection>
    <auth-constraint/>
</security-constraint>

但是,如果我包含 <http-method> 列出要阻止的各个方法,它会按预期工作,但我想知道为什么我的原始约束不起作用。

根据 Servlet XSD ...

<http-method>

Each http-method names an HTTP method to which the constraint applies.

<http-method-omission>

Each http-method-omission names an HTTP method to which the constraint does not apply.

通常您会同时使用两者,但会受到不同的限制。

举个例子(来自Jettywebdefault.xml

  <security-constraint>
    <web-resource-collection>
      <web-resource-name>Disable TRACE</web-resource-name>
      <url-pattern>/</url-pattern>
      <http-method>TRACE</http-method>
    </web-resource-collection>
    <auth-constraint/>
  </security-constraint>
  <security-constraint>
    <web-resource-collection>
      <web-resource-name>Enable everything but TRACE</web-resource-name>
      <url-pattern>/</url-pattern>
      <http-method-omission>TRACE</http-method-omission>
    </web-resource-collection>
  </security-constraint>

第一个约束拒绝 TRACE(空 auth-constraint)。
第二个约束启用除 TRACE 之外的所有方法。

确保您使用的是最新版本的 Jetty,因为在 9.4.38 之前的版本中存在遗漏的方法合并错误(它仅适用于根上下文)。

https://github.com/eclipse/jetty.project/issues/5909

我建议阅读那个问题,因为有很多关于行为的建议取决于 context-path(或不)、根 url-pattern、默认 url-pattern、基于上下文的 url-patterns 等。甚至是针对您的特定上下文替换 webdefault.xml 的配置。

您使用的是嵌入式 Jetty 吗?

如果是这样,您可能会发现使用 HttpConfiguration.Customizer 更容易。

package jetty;

import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.EnumSet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.HandlerList;

public class RejectHttpMethodsDemo
{
    public static class BanHttpMethods implements HttpConfiguration.Customizer
    {
        private final EnumSet<HttpMethod> bannedMethods;

        public BanHttpMethods(EnumSet<HttpMethod> bannedMethods)
        {
            this.bannedMethods = bannedMethods;
        }

        @Override
        public void customize(Connector connector, HttpConfiguration channelConfig, Request request)
        {
            HttpMethod httpMethod = HttpMethod.fromString(request.getMethod());
            if (bannedMethods.contains(httpMethod))
            {
                request.setHandled(true);
                request.getResponse().setStatus(HttpStatus.METHOD_NOT_ALLOWED_405);
            }
        }
    }

    public static void main(String[] args) throws Exception
    {
        Server server = new Server();

        HttpConfiguration httpConfig = new HttpConfiguration();
        httpConfig.addCustomizer(new BanHttpMethods(EnumSet.of(HttpMethod.TRACE, HttpMethod.MOVE)));
        ServerConnector connector = new ServerConnector(server, new HttpConnectionFactory(httpConfig));
        connector.setPort(9090);
        server.addConnector(connector);

        HandlerList handlers = new HandlerList();
        handlers.addHandler(new AbstractHandler()
        {
            @Override
            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
            {
                baseRequest.setHandled(true);
                response.setCharacterEncoding("utf-8");
                response.setContentType("text/plain");
                response.getWriter().printf("Hello, You asked to %s %s, that is all%n", baseRequest.getMethod(), baseRequest.getRequestURI());
            }
        });
        handlers.addHandler(new DefaultHandler());

        server.setHandler(handlers);
        server.start();

        try
        {
            HttpClient httpClient = HttpClient.newHttpClient();

            demoRequest(httpClient, server.getURI().resolve("/apple"), "GET");
            demoRequest(httpClient, server.getURI().resolve("/banana"), "TRACE");
            demoRequest(httpClient, server.getURI().resolve("/cherry"), "MOVE");
        }
        catch (Throwable t)
        {
            t.printStackTrace();
        }
        finally
        {
            server.stop();
        }
    }

    private static void demoRequest(HttpClient httpClient, URI path, String method)
    {
        try
        {
            HttpRequest httpRequest = HttpRequest.newBuilder(path)
                .method(method, HttpRequest.BodyPublishers.noBody())
                .build();
            HttpResponse<String> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
            System.out.printf("HTTP %s %s Response Status: %d%n", httpRequest.method(), httpRequest.uri(), httpResponse.statusCode());
            System.out.println(httpResponse.body());
        }
        catch (IOException | InterruptedException e)
        {
            e.printStackTrace();
        }
    }
}