【问题标题】:Setting up secure WebSocket server with Jetty and JavaScript client使用 Jetty 和 JavaScript 客户端设置安全的 WebSocket 服务器
【发布时间】:2016-04-17 19:12:04
【问题描述】:

我正在尝试使用 Jetty 设置一个安全的 WebSocket 服务器,如下所示:

import java.util.ArrayList;
import java.util.List;

import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
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.HandlerCollection;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.websocket.server.WebSocketHandler;
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;


public class WebSocketServer
{
    private Server server;
    private String host="localhost";
    private int port=8080;
    private String keyStorePath = "C:\\keystore";
    private String keyStorePassword="password";
    private String keyManagerPassword="password";
    private List<Handler> webSocketHandlerList = new ArrayList();
    MessageHandler messagehandler;

    public WebSocketServer()
    {
        System.out.println("WebSocketServer");

        server = new Server();

        // connector configuration
        SslContextFactory sslContextFactory = new SslContextFactory();
        sslContextFactory.setKeyStorePath(keyStorePath);
        sslContextFactory.setKeyStorePassword(keyStorePassword);
        sslContextFactory.setKeyManagerPassword(keyManagerPassword);
        SslConnectionFactory sslConnectionFactory = new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString());
        HttpConnectionFactory httpConnectionFactory = new HttpConnectionFactory(new HttpConfiguration());
        ServerConnector sslConnector = new ServerConnector(server, sslConnectionFactory, httpConnectionFactory);
        sslConnector.setHost(host);
        sslConnector.setPort(port);
        server.addConnector(sslConnector);

        // handler configuration
        HandlerCollection handlerCollection = new HandlerCollection();
        handlerCollection.setHandlers(webSocketHandlerList.toArray(new Handler[0]));
        server.setHandler(handlerCollection);

        WebSocketHandler wsHandler = new WebSocketHandler() {
            @Override
            public void configure(WebSocketServletFactory webSocketServletFactory) {
                webSocketServletFactory.register(MyWebSocketHandler.class);
            }
        };
        ContextHandler wsContextHandler = new ContextHandler();
        wsContextHandler.setHandler(wsHandler);
        wsContextHandler.setContextPath("/");  // this context path doesn't work ftm
        webSocketHandlerList.add(wsHandler);

        messagehandler = new MessageHandler();
        new Thread(messagehandler).start();

        try {
            server.start();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

使用以下命令创建密钥库文件,在 jdk/bin 文件夹中找到 here

keytool.exe -keystore keystore -alias jetty -genkey -keyalg RSA

之后我将文件移动到 C 目录以便于路径使用。

使用此配置,我的服务器似乎可以毫无问题地启动。所以我试图通过我的网站连接到它:

ws = new WebSocket("wss://localhost:8080/");

这根本不起作用。就像写的here一样,我想我必须配置SSL证书。此外,为了创建服务器,我使用了tutorial,而对于 java 客户端,他们实现了truststore。我是否必须为 JavaScript 做类似的事情?

【问题讨论】:

    标签: javascript java ssl websocket


    【解决方案1】:

    我想对 Riverhorse 的回答进行补充(我会添加评论,但在撰写本文时,我还没有 50 所需的声望来评论答案)。

    虽然该答案显示了如何使用自签名密钥完成该过程,但我想出了如何使用 CA 签名证书来完成此过程。

    为此,我假设您可以访问 3 个文件,这些文件是您收到的常用文件 购买 CA 签名证书时

    注意:您可能有 example.crt、example_key.txt 等文件 种变化。只要有内容就没有关系 匹配他们打算描述的文件。换句话说,所有的 文件只是文本,所以只要它们包含所需的文本 该特定文件,然后您可以使用该文件代替(或者您可以更改 名称)。

    example.cer -> the main certificate
    
    example.key -> the key for the certificate
    
    example.ca-bundle -> the intermediate certificate
    

    第一步是将主证书和中间证书合并到一个文件中

    注意顺序很重要,主证书应该在中间证书之前

    cat example.cer example.ca-bundle > cert-chain.txt
    

    现在下一步是使用 example.key 和新的 cert-chain.txt 生成一个 pkcs12 文件(然后可以将其放入密钥库中)。这些步骤与 Riverhorse 的回答非常相似。

    快跑

    每次它要求输入密码时,保持不变,你最终会在代码中使用它

    openssl pkcs12 -export -inkey example.key -in cert-chain.txt -out example.pkcs12
    

    最后一步是使用导入到密钥库中

    keytool -importkeystore -srckeystore example.pkcs12 -srcstoretype PKCS12 -destkeystore keystore
    

    如果您遇到unable to load certificates 140126084731328:error:0908F066:PEM routines:get_header_and_data:bad end line:../crypto/pem/pem_lib.c:842 之类的问题,请查看此答案的底部,了解可能对我有用的修复方法

    现在您拥有了 jetty 程序的密钥库,您可以回到 Riverhorse 的示例,看看如何应用它。

    如果您想检查密钥库是否具有可以运行的证书

    keytool -list -v -keystore keystore > output_filename.txt
    

    然后使用cat output_filename.txtnano output_filename.txt查看内容。

    这些步骤来自 Riverhorse 的回答以及下面的这 2 个链接

    https://www.thesslstore.com/knowledgebase/ssl-install/jetty-java-http-servlet-webserver-ssl-installation/

    在显示总体步骤方面确实很有帮助,但是直到将其与其他链接结合起来后我才发现成功(但是您的里程可能会有所不同,因为很可能我在第一次尝试时犯了错误)

    https://wiki.eclipse.org/Jetty/Howto/Configure_SSL#Requesting_a_Trusted_Certificate

    检查标题“通过 PKCS12 加载密钥和证书”,这个链接通常有点过时,因为此时使用 openssl 命令将 pkcs12 加载到密钥库中并不是一件事情,但它展示了如何处理中间最好的证书

    加载问题的潜在修复:

    如果您遇到此问题,可能导致该问题的原因之一是 cert-chain.txt 文件,打开它并检查两个证书之间的行是否相同(前后各 5 行)

    -----END CERTIFICATE----------BEGIN CERTIFICATE-----
    

    应该是这样的

    -----END CERTIFICATE-----
    -----BEGIN CERTIFICATE-----
    

    【讨论】:

      【解决方案2】:

      答案可能有点晚,但经过多次尝试,我已经成功了。


      首先要考虑一些一般性的考虑:

      • 作为要遵循的一般规则(也适用于 node.js),您必须首先使 HTTPS 工作以使 WSS 工作。 WebSocket 通过 HTTP 工作,因此如果您的服务器正确配置了 HTTPS(或 HTTP),那么将 WebSocket 添加到它将使 WSS(或 WS)工作。其实HTTPS/WSS和HTTP/WS都可以同时工作

      • 证书非常重要,因为并非所有类型的证书都适用于 Jetty(node.js 也是如此)。因此,您必须生成 Jetty 将接受的证书。

      • 使 HTTPS 工作对于自签名证书也很重要,因为您可能必须首先通过 HTTPS 访问服务器并添加异常,然后 WSS 才能工作(这可能取决于浏览器。)

      • 要考虑的另一件事是上下文路径也需要正确设置。我通过为 HTTP 和 WS 设置单独的路径来实现它,例如用于 HTTP(S) 的 /hello 和用于 WS(S) 的 /ws。可能可以对两者使用相同的上下文路径,但我还没有对此进行调查


      以下是我遵循的步骤。 我在 GitHub 上放了一个working example

      1) 使用来自here的命令生成正确的自签名证书

      上面的链接有一个关于如何生成正确的自签名证书的示例。 (如果你有 CA 签名的证书,我认为过程会有所不同。)

      我在此处粘贴命令以方便访问。请注意在被询问时始终提供相同的密码(包括最后步骤之一,当您输入的密码将以明文形式显示在屏幕上时)。

      openssl genrsa -aes256 -out jetty.key
      openssl req -new -x509 -key jetty.key -out jetty.crt
      keytool -keystore keystore -import -alias jetty -file jetty.crt -trustcacerts
      openssl req -new -key jetty.key -out jetty.csr
      openssl pkcs12 -inkey jetty.key -in jetty.crt -export -out jetty.pkcs12
      keytool -importkeystore -srckeystore jetty.pkcs12 -srcstoretype PKCS12 -destkeystore keystore
      

      2) 在 Jetty 中有正确的 HTTPS 代码。

      网络上有一些资源展示了如何使用 Jetty 进行 HTTPS,但对我来说只有一个有效,它是 here

      3) 拥有处理上下文的正确代码。

      这很难——Jetty 文档页面中的示例代码对我不起作用。 有效的是this。本教程还启发了我一个事实,即如果我尝试对 HTTP 和 WS 使用相同的路径,我可能会发生冲突。

      4) 最后,得到正确的 WebSocket 代码

      我找到了正确的 WebSocket 代码 here。有我们需要的是native-jetty-websocket-example

      【讨论】:

      • 哇,感谢您的工作!这个问题还在我的待办事项清单上。我将在下个月回到工作岗位时尝试你的答案。
      • 这拯救了我的一天。谢谢。
      猜你喜欢
      • 1970-01-01
      • 2017-09-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-09-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多