Issue
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.
Solution
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();
}
}
}
Answered By - Joakim Erdfelt
Answer Checked By - Cary Denson (JavaFixing Admin)