Friday Aug 02, 2013

Securing WebSocket applications on Glassfish

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.

About

Pavel Bucek

Search

Categories
Archives
« July 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
  
       
Today