我正在尝试使用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做类似的事情?
答案 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.txt
或 nano output_filename.txt
查看内容。
这些步骤来自riverhorse的回答以及下面的这两个链接
<块引用>在显示整体步骤方面确实很有帮助,但直到将它与另一个链接结合后我才发现成功(但是您的里程可能会有所不同,因为我在第一次尝试时很可能犯了错误)
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-----