使用Jetty和JavaScript客户端设置安全的WebSocket服务器

5
我正在尝试使用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 文件夹中找到的文档中创建密钥库文件:

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

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

通过这种配置,我的服务器似乎可以顺利启动。因此,我正在尝试像这样连接我的网站:

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

这根本不起作用。正如这里所写的那样,我认为我需要配置SSL证书。此外,为了创建服务器,我使用了这个教程,对于java客户端,他们实现了一个truststore。我需要为JavaScript做类似的事情吗?

2个回答

12

可能有点晚了,但我在多次尝试后使它工作了。


首先有几个一般性的考虑:

  • 作为一个通用的遵循规则(对于node.js也有效),你必须先让HTTPS工作才能让WSS工作。WebSocket 通过HTTP工作,所以如果你的服务器已经正确地配置了HTTPS(或HTTP),那么添加WebSocket将使WSS(或WS)生效。实际上,HTTPS/WSS和HTTP/WS可以同时工作。

  • 证书非常重要,因为不是所有种类的证书都适用于Jetty(对于node.js也是如此)。因此,你必须生成Jetty可接受的证书。

  • 使HTTPS工作对于自签名证书也很重要,因为你可能需要先通过HTTPS访问服务器,在WSS工作之前添加异常处理(这可能取决于浏览器)。

  • 另一个需要考虑的事情是上下文路径需要正确设置。我通过为HTTP和WS分别设置不同的路径来使其工作,例如 /hello 用于 HTTP(S),/ws 用于 WS(S)。也许可以使用相同的上下文路径来做到这一点,但我还没有调查过


以下是我所做的步骤。我在GitHub上放了一个可工作的示例

1) 使用这里的命令生成正确的自签名证书。

上面的链接有一个关于如何生成正确的自签名证书的示例(如果你有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的资源,但对我而言只有一个可用,就是这里

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

这个比较困难——来自Jetty文档页面的示例代码对我不起作用。 这里 的内容是有效的。这个教程也让我明白了如果我尝试在HTTP和WS中使用相同的路径可能会发生冲突。

4)最后,拥有正确的WebSocket代码

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


哇,谢谢你的工作!这个问题还在我的待办清单上。我打算下个月回到工作岗位时尝试你的答案。 - Steckdoserich
这个救了我的一天。谢谢。 - Scalpweb

2
我想补充一下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

如果你遇到类似于“无法加载证书 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的答案以及以下两个链接: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。一个潜在的解决方法是:如果你遇到了这个问题,可能是cert-chain.txt文件导致的,请打开它并检查两个证书之间的行是否类似(前后5行)。
-----END CERTIFICATE----------BEGIN CERTIFICATE-----

应该如此。
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接