Note: This article was originally authored by Romain Manni-Bucau in May of 2015 and has been updated by Bruno Baptista.
Tomitribe uses the WebSocket API to add security credentials to REST services in our support services. This article has been updated to the latest versions of WebSocket and other resources and shows how you can easily add authentication to your REST calls.
WebSockets are a good technical solution when there is a requirement for interactive communication. A typical example is a chat system, but it makes more sense for live updates such as the stock market. For example, 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, 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 HTTP Basic authentication security mechanism.
Note: As you may have guessed by its name, HTTP Basic authentication is very simple relying on a common security solution: using an HTTP header for authentication. The user of HTTP headers is a solution that can 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 Security Enforcement
To reuse the 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 filters, but before Servlet code is invoked. Generally, you want the Filter providing security to be one of the first filters invoked.
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. When you include a login-config
in the <code<web.xml file, the authentication takes place automatically before the filters. In this way, everything is in place 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 standard annotations. @ServerEndpoint
to declare a websocket and @OnOpen
to be executed when the connection is established.
@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 isn’t super complicated (a single class), we’ll secure the whole web application (/*
) without having to define http-method. However, if you want to define multiple security-constraints to handle a different configuration per endpoint, you can still use url-pattern configuration for servlets.
In relation to roles, 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 in.” As a final step, we can use Basic authentication to define a login-config
element by adding the auth-method element.
Ready to apply what was just explained about WebSocket? Lets securely lock it with Basic access authentication. To secure the WebSocket, using basic auth with standard servlets security, 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: The **
role, means that we want the user to be authenticated without any role constraint.
With an out of the box TomEE server, this will work with any users you have configured in your conf/tomcat-users.xml
file. You can also add your own.
Java Client
Next, we show the client side code that can be used to call our websocket endpoint.
We implement it as part of an Arquillian integration test, at the TribeWebSocketTest class, part of the demo project accompanying this post.
We use JUnit with Arquillian to start a TomEE server on a random port, with a specific allowed user. We choose username “Tomitribe” and the password “tomee”.
The url attribute will contain the details of the deployed server, namely the used http port.
@ArquillianResource()
private URL url;
The first thing we need to do is to add the Authorization
header to the first request. To do so, the WebSoclet JSR provides the ClientEndpointConfig.Configurator
. This gives you the beforeRequest(header)
hook where you can add the headers you want, including the Authorization
header. Note that we choose to set username as “Tomitribe” and the password as “tomee”
ClientEndpointConfig.Configurator configurator = new ClientEndpointConfig.Configurator() {
public void beforeRequest(Map<String, List> 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:" + url.getPort() + "/app/socket"));
After having created the authorizationConfiguration
object, we need to open a session to the websocket address, but for that we need an endpoint object to handle the response, this is how we configure the client side message handler:
ClientEndpointConfig.Configurator configurator = new ClientEndpointConfig.Configurator() {
public void beforeRequest(Map<String, List> 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:" + url.getPort() + "/app/socket"));
Once we open a session, we add a handler that stores the response message in a thread safe way with AtomicReference<String>
. We also add a CountDownLatch
to wait for some time or until we get one response.
<b<Tip: To ensure the endpoint is secured, you can write another test removing the following line:
headers.put("Authorization", asList("Basic " +
printBase64Binary("wrongUser:wrongPassword".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 🙂