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}
我试图在嵌入式 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}