【问题标题】:Embedded Jetty: In secure https server, ContextHandler redirects to http URIEmbedded Jetty:在安全的 https 服务器中,ContextHandler 重定向到 http URI
【发布时间】:2013-12-09 04:00:20
【问题描述】:

我正在使用嵌入式 Jetty 构建沙盒 RESTful API。我的概念验证设计:一个简单的嵌入式码头服务器,它 (1) 接受 SSL 端口上的连接,(2) 使用 ContextHandlerCollection 根据 URI 前缀调用适当的处理程序。

我的原始测试使用简单的非 SSL 连接,似乎运行良好(注意,附录中的导入代码和助手 HelloHandler 类)。

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

    ContextHandler test1Context = new ContextHandler();
    test1Context.setContextPath("/test1");
    test1Context.setHandler(new HelloHandler("Hello1"));

    ContextHandler test2Context = new ContextHandler();
    test2Context.setContextPath("/test2");
    test2Context.setHandler(new HelloHandler("Hello2"));

    ContextHandlerCollection contextHandlers = new ContextHandlerCollection();
    contextHandlers.setHandlers(new Handler[] { test1Context, test2Context });

    server.setHandler(contextHandlers);
    server.start();
    server.join();
}

但是,在对此进行测试时,我没有注意到当我省略了尾部正斜杠时正在发生浏览器重定向,因此 http://localhost:12000/test1 被重定向到 http://localhost:12000/test1/。 (FWIW,那个 oversite 后来会转化为 4 多个小时的故障排除)。

当我切换到 HTTPS SSL 连接时,一切都出错了。代码如下:

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

    // Setups
    SslContextFactory sslContextFactory = new SslContextFactory();
    sslContextFactory.setKeyStorePath("C:/keystore.jks");
    sslContextFactory.setKeyStorePassword("password");
    sslContextFactory.setKeyManagerPassword("password");

    ContextHandler test1Context = new ContextHandler();
    test1Context.setContextPath("/test1");
    test1Context.setHandler(new HelloHandler("Hello1"));

    ContextHandler test2Context = new ContextHandler();
    test2Context.setContextPath("/test2");
    test2Context.setHandler(new HelloHandler("Hello2"));

    ContextHandlerCollection contextHandlers = new ContextHandlerCollection();
    contextHandlers.setHandlers(new Handler[] { test1Context, test2Context });

    ServerConnector serverConnector = new ServerConnector(server,
            new SslConnectionFactory(sslContextFactory, "http/1.1"),
            new HttpConnectionFactory());

    serverConnector.setPort(12000);
    server.setConnectors(new Connector[] { serverConnector });
    server.setHandler(contextHandlers);

    server.start();
    server.join();
}

症状:

尝试使用https://localhost:12000/test1(没有尾部斜杠)会导致浏览器报告服务器已重置连接。此外,我最初所做的 not 发现,URI 被重定向到 http://localhost:12000/test1/(不是 https!)。有趣的是(以虐待狂幽默的方式),有几次,我会更改代码中无关紧要的东西,然后无意中用https://localhost:12000/test1/ 进行测试,它会起作用。这种误报造成的挫败感无法用言语来形容。

除了浏览器重定向和报告连接重置错误之外,我的服务器日志中还会出现以下异常:

2013-11-23 13:57:48 DEBUG org.eclipse.jetty.server.HttpConnection:282 - 
org.eclipse.jetty.io.EofException
    at org.eclipse.jetty.io.ssl.SslConnection$DecryptedEndPoint.fill(SslConnection.java:653)
    at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:240)
    at org.eclipse.jetty.io.AbstractConnection$ReadCallback.run(AbstractConnection.java:358)
    at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:601)
    at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:532)
    at java.lang.Thread.run(Thread.java:722)
Caused by: javax.net.ssl.SSLException: Unrecognized SSL message, plaintext connection?
    at sun.security.ssl.EngineInputRecord.bytesInCompletePacket(EngineInputRecord.java:171)
    at sun.security.ssl.SSLEngineImpl.readNetRecord(SSLEngineImpl.java:845)
    at sun.security.ssl.SSLEngineImpl.unwrap(SSLEngineImpl.java:758)
    at javax.net.ssl.SSLEngine.unwrap(SSLEngine.java:624)
    at org.eclipse.jetty.io.ssl.SslConnection$DecryptedEndPoint.fill(SslConnection.java:518)
    ... 5 more

不幸的是,当关键线索在浏览器重定向的 URL 中时,我花了所有时间尝试直接解决此异常。事实证明,ContextHandler 代码有一个默认行为,当不存在尾部正斜杠时,它会导致它仅重定向到具有尾部斜杠的 URI。不幸的是,这个重定向是一个 HTTP URI - 所以 HTTPS 方案被丢弃,这导致服务器抱怨纯文本。

解决方法:

一旦这种重定向行为对我来说很清楚,在 Google 上对该实际问题进行快速搜索后,我就找到了 ContextHandler.setAllowNullPathInfo(true) 方法——它关闭了这种重定向行为。在我上面的代码中,这是通过 2 行来完成的:

test1Context.setAllowNullPathInfo(true);
test2Context.setAllowNullPathInfo(true);

这篇文章的主要观点:

我花了 3 到 4 个小时试图解决“javax.net.ssl.SSLException: Unrecognized SSL message, plaintext connection?”异常,并且我在网络上没有找到与上述解决方案/解决方法相关联的异常。如果我能从我所经历的挫折中拯救另一位开发人员,那么任务就完成了。

悬而未决的问题(发布此问题的其他原因): 好的,所以我得到了这段代码,但必须承认:我非常不安,因为我非常简单的概念验证测试,做一些我认为很常见的事情,遇到了这种似乎完全前所未有的情况.这告诉我,我可能正在做一些超出“最佳实践”范围的事情;或者,更糟糕的是,在我设计它的过程中做了一些完全错误的事情。所以,问题:

1) 我做错了吗?

2) 为什么 ContextHandler 的默认行为是重定向缺少尾随空格的 URI?使用 setAllowNullPathInfo(true) 覆盖默认行为会招致什么风险?

附录(帮助类和导入的代码) 进口: 导入 java.io.IOException;

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

import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Handler;
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.SslConnectionFactory;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.util.ssl.SslContextFactory;

助手类:

static class HelloHandler extends AbstractHandler {
    final String _greeting;

    public HelloHandler(String greeting) {
        _greeting = greeting;
    }

    public void handle(String target, Request baseRequest,
            HttpServletRequest request, HttpServletResponse response)
            throws IOException, ServletException {
        response.setContentType("text/html;charset=utf-8");
        response.setStatus(HttpServletResponse.SC_OK);
        baseRequest.setHandled(true);
        response.getWriter().println("<h1>" + _greeting + "</h1>");
    }
}

【问题讨论】:

    标签: rest ssl jetty embedded-jetty http-redirect


    【解决方案1】:

    您缺少 HttpConfiguration 设置。

    这里...

    package jetty.examples;
    
    import org.eclipse.jetty.server.Connector;
    import org.eclipse.jetty.server.Handler;
    import org.eclipse.jetty.server.HttpConfiguration;
    import org.eclipse.jetty.server.HttpConnectionFactory;
    import org.eclipse.jetty.server.SecureRequestCustomizer;
    import org.eclipse.jetty.server.Server;
    import org.eclipse.jetty.server.ServerConnector;
    import org.eclipse.jetty.server.SslConnectionFactory;
    import org.eclipse.jetty.server.handler.ContextHandler;
    import org.eclipse.jetty.server.handler.ContextHandlerCollection;
    import org.eclipse.jetty.util.ssl.SslContextFactory;
    
    public class SecureContexts
    {
        public static void main(String[] args) throws Exception
        {
            Server server = new Server();
            int port = 12000;
    
            // Setup SSL
            SslContextFactory sslContextFactory = new SslContextFactory();
            sslContextFactory.setKeyStorePath(System.getProperty("jetty.keystore.path","C:/keystore.jks"));
            sslContextFactory.setKeyStorePassword(System.getProperty("jetty.keystore.password","password"));
            sslContextFactory.setKeyManagerPassword(System.getProperty("jetty.keymanager.password","password"));
    
            // Setup HTTP Configuration
            HttpConfiguration httpConf = new HttpConfiguration();
            httpConf.setSecurePort(port);
            httpConf.setSecureScheme("https");
            httpConf.addCustomizer(new SecureRequestCustomizer());
    
            ContextHandler test1Context = new ContextHandler();
            test1Context.setContextPath("/test1");
            test1Context.setHandler(new HelloHandler("Hello1"));
    
            ContextHandler test2Context = new ContextHandler();
            test2Context.setContextPath("/test2");
            test2Context.setHandler(new HelloHandler("Hello2"));
    
            ContextHandlerCollection contextHandlers = new ContextHandlerCollection();
            contextHandlers.setHandlers(new Handler[]
            { test1Context, test2Context });
    
            ServerConnector serverConnector = new ServerConnector(server,
                new SslConnectionFactory(sslContextFactory,"http/1.1"),
                new HttpConnectionFactory(httpConf)); // <-- use it!
            serverConnector.setPort(port);
    
            server.setConnectors(new Connector[]
            { serverConnector });
            server.setHandler(contextHandlers);
    
            server.start();
            server.join();
        }
    }
    

    【讨论】:

    • 成功了!该死,我什至尝试过,但省略了 httpConf.addCustomizer(new SecureRequestCustomizer()) 步骤;所以添加没有影响,似乎是多余的。
    猜你喜欢
    • 1970-01-01
    • 2011-09-18
    • 1970-01-01
    • 2013-03-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多