Java 嵌入式码头正在接受 HTTP TRACE 方法

Java embedded jetty is accepting HTTP TRACE method

我试图在嵌入式 Jetty 中禁用 HTTP TRACE 方法。在 Jetty 文档中,HTTP 跟踪在默认情况下是禁用的,但对于嵌入式它仍然是启用的。我试图像 jetty.xml.

中那样将跟踪禁用为安全约束
    ServletContextHandler servletHandler = new ServletContextHandler(ServletContextHandler.SESSIONS | ServletContextHandler.SECURITY);
    servletHandler.setClassLoader(Server.class.getClassLoader());
    servletHandler.setContextPath("/");
    servletHandler.addEventListener(new ContextLoaderListener());
    servletHandler.addServlet(new ServletHolder(new CXFServlet()), "/*");
    servletHandler.setInitParameter("contextClass", AnnotationConfigWebApplicationContext.class.getName());
    servletHandler.setInitParameter("contextConfigLocation", BeansConfig.class.getName());
    servletHandler.setInitParameter("javax.ws.rs.Application", DispatcherConfig.class.getName());

     /*
     * <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>
     */
     Constraint constraint = new Constraint();
     constraint.setName("Disable TRACE");

     ConstraintMapping mapping = new ConstraintMapping();
     mapping.setConstraint(constraint);
     mapping.setMethod("TRACE");
     mapping.setPathSpec("/"); // this did not work same this mapping.setPathSpec("/*");

     ConstraintSecurityHandler securityHandler = (ConstraintSecurityHandler) servletHandler.getSecurityHandler();
     securityHandler.addConstraintMapping(mapping);

soapUI 的示例输出:

HTTP/1.1 200 OK
Content-Type: message/http
Content-Length: 143
Server: Jetty(9.0.6.v20130930)

TRACE / HTTP/1.1
Connection: keep-alive
User-Agent: Apache-HttpClient/4.1.1 (java 1.5)
Host: 192.168.33.115
Accept-Encoding: gzip,deflate

在您的 Constraint 对象上,您需要调用 setAuthenticate(true),并确保您不调用 setRoles(String[])。这使得它等同于带有空 <auth-constraint><security-constraint>,它禁止访问。

它使用 DefaultServlet 而不是 CXFServlet 的原因是因为 DefaultServlet 特别拒绝访问 TRACE 方法。

扩展服务器 class 并覆盖 handle() 方法对我来说效果最好。

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;

import org.eclipse.jetty.server.HttpChannel;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.Server;

public class MyServer extends Server {

    @Override
    public void handle(HttpChannel<?> connection) throws IOException, ServletException {
        Request request=connection.getRequest();
        Response response=connection.getResponse();

        if ("TRACE".equals(request.getMethod())){
            request.setHandled(true);
            response.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
        } else {
            super.handle(connection);
        }
    }
}

Jan 的回答有效(当然)。但是,它会导致 jetty 在启动时打印警告。

WARNING ... SecurityHandler ... has uncovered http methods for path ...

为了避免这种情况,首先像 Jan 的回答一样添加约束(禁用 TRACE),然后添加另一个约束(允许除 TRACE 之外的所有内容)。我为每个 ServletContextHandler:

调用以下代码
private void disableTraceMethodForHandler(final ServletContextHandler servletContextHandler) {
    SecurityHandler securityHandler = servletContextHandler.getSecurityHandler();
    if (securityHandler == null) {
        securityHandler = new ConstraintSecurityHandler();
        servletContextHandler.setSecurityHandler(securityHandler);
    }
    if (securityHandler instanceof ConstraintSecurityHandler) {
        ConstraintSecurityHandler constraintSecurityHandler = (ConstraintSecurityHandler) securityHandler;

        ConstraintMapping disableTraceMapping = new ConstraintMapping();
        Constraint disableTraceConstraint = new Constraint();
        disableTraceConstraint.setName("Disable TRACE");
        disableTraceConstraint.setAuthenticate(true);
        disableTraceMapping.setConstraint(disableTraceConstraint);
        disableTraceMapping.setPathSpec("/");
        disableTraceMapping.setMethod("TRACE");
        constraintSecurityHandler.addConstraintMapping(disableTraceMapping);

        ConstraintMapping enableEverythingButTraceMapping = new ConstraintMapping();
        Constraint enableEverythingButTraceConstraint = new Constraint();
        enableEverythingButTraceConstraint.setName("Enable everything but TRACE");
        enableEverythingButTraceMapping.setConstraint(enableEverythingButTraceConstraint);
        enableEverythingButTraceMapping.setMethodOmissions(new String[] {"TRACE"});
        enableEverythingButTraceMapping.setPathSpec("/");
        constraintSecurityHandler.addConstraintMapping(enableEverythingButTraceMapping);
    }
}

我在一月份找到 this issue in Google's appengine-java-vm-runtime and the fix 后找到了这个解决方案。上面的代码应该与那里的 XML 配置相同。

您可以创建过滤器:

...
@Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        if ("TRACE".equalsIgnoreCase(httpRequest.getMethod())) {
            httpResponse.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
            return;
        }
        chain.doFilter(request, response);
    }
....

您可以使用 HttpConfiguration.Customizer.

而不是使用约束

好处是这将适用于所有请求,即使它们不属于上下文,并且也适用于所有特定于上下文的 Web 应用程序。

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

有输出...

2022-01-21 11:23:13.718:INFO::main: Logging initialized @210ms to org.eclipse.jetty.util.log.StdErrLog
2022-01-21 11:23:13.775:INFO:oejs.Server:main: jetty-9.4.43.v20210629; built: 2021-06-30T11:07:22.254Z; git: 526006ecfa3af7f1a27ef3a288e2bef7ea9dd7e8; jvm 11.0.12+7
2022-01-21 11:23:13.809:INFO:oejs.AbstractConnector:main: Started ServerConnector@4fcd19b3{HTTP/1.1, (http/1.1)}{0.0.0.0:9090}
2022-01-21 11:23:13.812:INFO:oejs.Server:main: Started @308ms
HTTP GET http://127.0.1.1:9090/apple Response Status: 200
Hello, You asked to GET /apple, that is all

HTTP TRACE http://127.0.1.1:9090/banana Response Status: 405

HTTP MOVE http://127.0.1.1:9090/cherry Response Status: 405

2022-01-21 11:23:14.186:INFO:oejs.AbstractConnector:main: Stopped ServerConnector@4fcd19b3{HTTP/1.1, (http/1.1)}{0.0.0.0:9090}