Securing WebSocket applications on Glassfish
- by Pavel Bucek
Today we are going to cover deploying secured WebSocket applications on Glassfish and access to these services using WebSocket Client API.
WebSocket server application setup
Our server endpoint might look as simple as this:
@ServerEndpoint("/echo")
public class EchoEndpoint {
@OnMessage
public String echo(String message) {
return message + " (from your server)";
}
}
Everything else must be configured on container level.
We
can start with enabling SSL, which will require web.xml to be added to
your project. For starters, it might look as following:
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee">
<security-constraint>
<web-resource-collection>
<web-resource-name>Protected resource</web-resource-name>
<url-pattern>/*</url-pattern>
<http-method>GET</http-method>
</web-resource-collection>
<!-- https -->
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
</web-app>
This is minimal web.xml for this task - web-resource-collection just defines URL pattern and HTTP method(s) we want to put a constraint on and user-data-constraint defines that constraint, which is in our case transport-guarantee. More information about these properties and security settings for web application can be found in Oracle Java EE 7 Tutorial.
I
have some simple webpage attached as well, so I can test my endpoint
right away. You can find it (along with complete project) in Tyrus workspace: [webpage] [whole project].
After
deploying this application to Glassfish Application Server, you should
be able to hit it using your favorite browser. URL where my application
resides is https://localhost:8181/sample-echo-https/
(may be different, depends on other configuration). My browser warns me
about untrusted certificate (I use what freshly built Glassfish
provides - self signed certificates) and after adding an exception for
this site, I can see my webpage and I am able to securely connect to wss://localhost:8181/sample-echo-https/echo.
WebSocket client
Already mentioned demo application
also contains test client, but execution of this is skipped for normal
build. Reason for this is that Glassfish uses these self-signed "random"
untrusted certificates and you are (in most cases) not able to connect
to these services without any additional settings.
Creating test
WebSocket client is actually quite similar to server side, only
difference is that you have to somewhere create client container and
invoke connect with some additional info. Java API for WebSocket allows
you to use annotated and programmatic way to construct endpoints. Server
side shows the annotated case, so let's see how the programmatic
approach will look.
final WebSocketContainer client = ContainerProvider.getWebSocketContainer();
client.connectToServer(new Endpoint() {
@Override
public void onOpen(Session session, EndpointConfig EndpointConfig) {
try {
// register message handler - will just print out the
// received message on standard output.
session.addMessageHandler(new MessageHandler.Whole<String>() {
@Override
public void onMessage(String message) {
System.out.println("### Received: " + message);
}
});
// send a message
session.getBasicRemote().sendText("Do or do not, there is no try.");
} catch (IOException e) {
// do nothing
}
}
}, ClientEndpointConfig.Builder.create().build(),
URI.create("wss://localhost:8181/sample-echo-https/echo"));
This
client should work with some secured endpoint with valid certificated
signed by some trusted certificate authority (you can try that with wss://echo.websocket.org).
Accessing our Glassfish instance will require some additional settings.
You can tell Java which certificated you trust by adding
-Djavax.net.ssl.trustStore property (and few others in case you are
using linked sample).
Complete command line when you are testing your service might need to look somewhat like:
mvn clean test -Djavax.net.ssl.trustStore=$AS_MAIN/domains/domain1/config/cacerts.jks\
-Djavax.net.ssl.trustStorePassword=changeit -Dtyrus.test.host=localhost\
-DskipTests=false
Where AS_MAIN points to your Glassfish instance.
Note: you might need to setup keyStore and trustStore per client instead of per JVM; there is a way how to do it, but it is Tyrus proprietary feature: http://tyrus.java.net/documentation/1.2.1/user-guide.html#d0e1128.
And that's it! Now nobody is able to "hear" what you are sending to or receiving from your WebSocket endpoint.
There
is always room for improvement, so the next step you might want to take
is introduce some authentication mechanism (like HTTP Basic or Digest).
This topic is more about container configuration so I'm not going to go
into details, but there is one thing worth mentioning: to access
services which require authorization, you might need to put this
additional information to HTTP headers of first (Upgrade) request (there
is not (yet) any direct support even for these fundamental mechanisms,
user need to register Configurator and add headers in beforeRequest method invocation). I filed related feature request as TYRUS-228; feel free to comment/vote if you need this functionality.