Sam Daniel
Sam Daniel

Reputation: 1902

Excluding HTTP methods using security constraint, jetty

I am trying to prevent all http methods other than GET, PUT and POST accessing my web app with this below security-constraint but it forbids all HTTP methods, even the methods omitted from this constraint.

    <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>

However, it works as expected if I include <http-method> list individual methods to be prevented, but I am wondering why did my original constraint didn't work.

Upvotes: 1

Views: 1766

Answers (1)

Joakim Erdfelt
Joakim Erdfelt

Reputation: 49462

Per the 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.

Usually you use both, but in different constraints.

Take this example (from the Jetty webdefault.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>

The first constraint rejects TRACE (with an empty auth-constraint).
The second constraint enables all methods but TRACE.

Make sure you are using a recent version of Jetty, as there is a omitted method merge bug in versions prior to 9.4.38 (it only works on root context).

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

I would recommend reading that issue, as there's plenty of suggestions on behaviors depending on context-path (or not), root url-pattern, default url-pattern, context based url-patterns, etc. Even configurations that replace the webdefault.xml for your specific context.

Are you using Embedded Jetty?

If so, you might find using an HttpConfiguration.Customizer easier.

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

Upvotes: 1

Related Questions