嵌入式Jetty:在安全的https服务器中,ContextHandler重定向到http URI

时间:2013-11-24 14:50:44

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

我正在使用嵌入式Jetty构建沙箱RESTful API。我的概念验证设计:一个简单的嵌入式jetty服务器,它(1)接受SSL端口上的连接,(2)使用ContextHandlerCollection根据URI前缀调用正确的Handler。

我使用简单的非SSL连接进行的原始测试似乎完美无缺(注意,导入代码和APPENDIX中的帮助程序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,这种过时会导致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(没有尾部斜杠)会导致浏览器报告服务器已重置连接。另外,我最初点的地方,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方案被删除,导致服务器抱怨纯文本。

替代方法:

一旦这个重定向行为变得清晰,我快速谷歌搜索该实际问题就会引导我进入ContextHandler.setAllowNullPathInfo(true)方法 - 这会关闭这种重定向行为。在上面的代码中,这是通过2行完成的:

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

此帖的主要内容:

我花了3-4个小时试图解决“javax.net.ssl.SSLException:无法识别的SSL消息,明文连接?”异常,并且我在网络上没有找到与上述解决方案/解决方法相关联的异常。如果我从其经历的挫折中拯救了另外一个开发人员,那么任务就完成了。

引发问题(发布此问题的其他原因): 好吧,所以我得到了这段代码,但不得不承认:我非常不安的事实是,我非常简单的概念验证测试,做了我认为很常见的事情,遇到了这种情况,这似乎是前所未有的。这告诉我,我可能正在做一些超越“最佳实践”领域的事情;或者更糟糕的是,在我设计这个方面做了一些完全错误的事情。所以,问题:

1)我做错了吗?

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

APPENDIX(帮助程序类和导入代码) 进口:     import 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>");
    }
}

1 个答案:

答案 0 :(得分:4)

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