Skip to main content

WebSockets are a good technical solution where there is a requirement for interactive communication. A typical example is a chat system, but it makes much better sense for live updates such as the stock market.  Being able to see share prices go from red to green is a “must have” for stock traders.

The WebSocket API is quite easy to use, but when it comes to security you don’t have a lot of options. However, don’t forget the first step in the WebSocket protocol is to upgrade a HTTP request. If you have a HTTP request, you should be able to use HTTP security mechanisms.  Let’s try it out using the Basic security mechanism.

Note: As you may have guessed by its name, HTTP Basic authentication is very simple. It actually uses a very common pattern, which is to rely on a header for authentication so this solution could be generalized quite easily. For example, you could use the same technique with other header based authentication mechanisms, such as an OAuth bearer token.

A Look at WebSocket Implementation

To let us reuse HTTP security mechanism, we need to ensure we are authenticated before the WebSocket endpoint is activated. The WebSocket specification is implemented (in Tomcat, TomEE, WildFly, etc.) using a Servlet Filter most of the time.

Unless you install another Filter in the chain after the WebSocket Filter, the WebSocket  Filter is implemented after all other  Filter, but before Servlet code is invoked. Generally, you want the Filter providing security to be one of the first Filter invoked, so this does not present as a constraint.

Note: In Tomcat and TomEE, it is also common to use a Valve to replace the security Filter to allow authentication to take place even before the  Filter chain. For instance, this is done automatically if you configure a login-config in your web.xml.

Everything is all ready to let us reuse Servlet container security. Let’s try it!

WebSocket Server Endpoint

For our example, we’ll write a simple WebSocket server endpoint sending “Hello <username>” when the session is opened.

To keep it simple we’ll use the annotation API:

@ServerEndpoint(value = "/socket")
public class TribeWebSocket {
    @OnOpen
    public void onOpen(final Session session) throws Exception {
        session.getBasicRemote().sendText("Hello " + session.getUserPrincipal().getName());
    }
}

Securing WebSocket using Basic Access Authentication

Now that we have an endpoint, let’s apply Basic access authentication security.

WebSocket (JSR 356, Java API for WebSocket) is clever in reusing most of the Servlet container security mechanisms. This means you can define a security-constraint in your web.xml. Note however that only GET http-method applies to WebSocket endpoints.

Since our application is super complicated (a single class), we’ll secure the whole web application (/*) but without having to define http-method. However, if you want to define multiple security-constraint to handle a different configuration by endpoint, you can still use url-pattern configuration for servlets.

In term of role, we don’t need validation but we will want to authenticate users. To do this, we’ll use the meta-role ** as auth-constraint, which just means “the user is logged.” As a final step, we can use Basic authentication to define a login-config with this auth-method.

Ready to apply what was just explained about WebSocket? Lets securely lock it with Basic access authentication.

To secure WebSocket, standard servlets security works, but not that only GET is supported as  http-method. To use Basic we will just add our  web.xml, which looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.1"
         xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd">
  <security-constraint>
    <web-resource-collection>
      <web-resource-name>ws-security</web-resource-name>
      <url-pattern>/*</url-pattern>
    </web-resource-collection>
    <auth-constraint>
      <role-name>**</role-name>
    </auth-constraint>
  </security-constraint>
  <login-config>
    <auth-method>BASIC</auth-method>
  </login-config>
</web-app>

Note ** as role, means that we only want the user to be authenticated without any role constraint.

Java Client

Now our server secured, how can we connect to it with a WebSocket client? Only thing we need is to add the  Authorization header to the first request.

To do so, the WebSoclet JSR provides  ClientEndpointConfig.Configurator. This gives you the  beforeRequest(header) hook where you can add headers you want:

ClientEndpointConfig.Configurator configurator = new ClientEndpointConfig.Configurator() {
    public void beforeRequest(Map<String, List<String>> headers) {
        headers.put("Authorization", asList("Basic " + DatatypeConverter.printBase64Binary("user:password".getBytes())));
    }
};

To pass the configurator to the client container we need to use an instance of  ClientendpointConfig. To make it easy to build we can use the associated builder:

ClientEndpointConfig clientConfig = ClientEndpointConfig.Builder.create()
        .configurator(configurator)
        .build();

Finally, we just need to connect to the server:

URI uri = new URI("...");
ClientEndpointConfig.Configurator configurator = new ClientEndpointConfig.Configurator() {
    public void beforeRequest(Map> headers) {
        headers.put("Authorization", asList("Basic " + DatatypeConverter.printBase64Binary("user:password".getBytes())));
    }
};
ClientEndpointConfig clientConfig = ClientEndpointConfig.Builder.create()
        .configurator(configurator)
        .build();

WebSocketContainer container = ContainerProvider.getWebSocketContainer();
Session client = container.connectToServer(new MyEndpoint(), clientConfig, uri);

// do something

client.close();

If you prefer the annotation version, you’ll get something like:

@ClientEndpoint(configurator = AuthorizationConfigurator.class)
public class Client {
    @OnMessage
    public void onMessage(String content) {
        // got a message            
    }
}

public class AuthorizationConfigurator extends ClientEndpointConfig.Configurator {
    @Override
    public void beforeRequest(Map> headers) {
        headers.put("Authorization", asList("Basic " + printBase64Binary("Tomitribe:tomee".getBytes())));
    }
}

Note: As you can see the issue with the annotation solution is that you can’t easily pass parameters to the Configurator where it would be quite easy to do it with the programmatic API (and create a generic AuthorizationHeaderConfigurator).

Testing Basic authentication for WebSocket

Didn’t believe it was that easy? To ensure it works, we’ll write a simple test: a client named “Tomitribe” will connect to the server endpoint and ensure he got “Hello Tomitribe” as the message.

To do so, we’ll simply use the brand new TomEE embedded JUnit rule for simplicity, but an Arquillian test would work as well. We’ll simply use the default setup, which is to deploy the Classpath as a WebApp. The only configuration we’ll do is to use a random port (to support parallel testing) and configure a user “Tomitribe” to be able to test our security.

All you need with this JUnit rule is to define it as public in your test class:

@Rule
public TomEEEmbeddedRule server =
        new TomEEEmbeddedRule(new Configuration().randomHttpPort().user("Tomitribe", "tomee"), "");

Then we just use a client close to the one we spoke about in previous part to connect,  capture and validate the first message sent by the server:

@Test
public void sayHi() throws Exception {
    final AtomicReference message = new AtomicReference<>();
    final CountDownLatch latch = new CountDownLatch(1);
    Endpoint endpoint = new Endpoint() {
        @Override
        public void onOpen(Session session, EndpointConfig config) {
            session.addMessageHandler(new MessageHandler.Whole() {
                @Override
                public void onMessage(String content) {
                    message.set(content);
                    latch.countDown();
                }
            });
        }
    };

    ClientEndpointConfig.Configurator configurator = new ClientEndpointConfig.Configurator() {
        public void beforeRequest(Map> headers) {
            headers.put("Authorization", asList("Basic " + printBase64Binary("Tomitribe:tomee".getBytes())));
        }
    };
    ClientEndpointConfig authorizationConfiguration = ClientEndpointConfig.Builder.create()
            .configurator(configurator)
            .build();

    Session session = ContainerProvider.getWebSocketContainer()
            .connectToServer(
                    endpoint, authorizationConfiguration,
                    new URI("ws://localhost:" + server.getPort() + "/socket"));

    latch.await(1, TimeUnit.MINUTES);
    session.close();

    assertEquals("Hello Tomitribe", message.get());
}

Tip: To ensure the endpoint is secured, you can write another test removing the following line:

headers.put("Authorization", asList("Basic " + printBase64Binary("Tomitribe:tomee".getBytes())));

And you’ll get the expected 401 exception which proves the server is secured:

javax.websocket.DeploymentException: The HTTP response from the server [HTTP/1.1 401 Unauthorized
] did not permit the HTTP upgrade to WebSocket
	at org.apache.tomcat.websocket.WsWebSocketContainer.parseStatus

Give it a try – WebSocket secured by Basic authentication

If you want to play further with this post example, you can find the sources on GitHub at: https://github.com/tomitribe/secured-websocket.

 

Stay connected 😉

Leave a Reply