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

时间:2016-01-12 23:58:53

标签: javascript java ssl websocket

我正在尝试使用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做类似的事情?

2 个答案:

答案 0 :(得分:8)

答案可能有点迟,但我经过多次尝试后才能使用。

首先要考虑一些因素:

  • 作为遵循的一般规则(对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

答案 1 :(得分: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 的问题,请查看此答案的底部,以获取对我有用的潜在修复

现在您拥有了码头程序的密钥库,您可以返回到 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

<块引用>

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

加载问题的潜在修复:

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

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

应该的

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