Setting up a secure WebSocket server with Jetty and a JavaScript client
I'm trying to setup a secure WebSocket server with Jetty like this:
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();
}
}
}
Create the keystore file found here in the JDK/bin folder using the following command:
keytool.exe -keystore keystore -alias jetty -genkey -keyalg RSA
After that I moved the file into the C directory for easy use of the path.
With this configuration, my server seems to start fine. So I am trying to connect to it through my website like this:
ws = new WebSocket("wss://localhost:8080/");
This doesn't work at all. Like it 's written here , I think I have to configure an SSL certificate. Also, to create the server I used this tutorial and for the Java client they implemented one truststore
. Do I have to do something similar with JavaScript?
It might be too late for an answer, but after many attempts I've made it work.
First note the following:
As a general rule to follow (also valid for node.js), HTTPS must be enabled first for WSS to work. WebSocket runs over HTTP, so if the server has HTTPS (or HTTP) properly configured, adding a WebSocket to it will make WSS (or WS) work. Actually, HTTPS/WSS and HTTP/WS can work at the same time
Certificates are very important because not all types of certificates will work with Jetty (node.js too). So you have to generate a certificate that Jetty will accept.
Getting HTTPS to work is also important for self-signed certificates, as you may have to first access the server over HTTPS and add an exception before WSS works (this may depend on the browser.)
Another thing to consider is that the context path also needs to be set correctly. I got it working by setting up separate paths for HTTP and WS, like
/hello
HTTP(S) and/ws
WS(S). Both can probably be done with the same context path, but I haven't investigated this yet
Below are the steps I followed. I put a working example on GitHub .
1) Use the command here to generate the correct self-signed certificate
The link above provides an example on how to generate the correct self-signed certificate. (I think the process is different if you have a CA-signed certificate.)
I'm pasting the command here for easy access. Note that always provide the same password when asked (including the last step, when the password you typed will appear in clear text on the screen).
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) The correct code to have HTTPS in Jetty.
There are some resources online showing how to use Jetty for HTTPS, but only one worked for me, right here .
3) Have the correct code for handling the context.
It's difficult - the example code on the Jetty documentation page didn't work for me. What work is like this . The tutorial also enlightened me on the fact that if I try to use the same path for HTTP and WS, there might be a conflict.
4) Finally, get the correct WebSocket code
I found the correct WebSocket code here . What we need is .native-jetty-websocket-example