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.