Integrating TOTP with Java EE’s Container Managed Security

clip_image002Edited on Monday Febuary 1 2014

After reading the following blog on time-based one time password (TOTP), I wanted to see if I could add an additional layer of security into Java EE’s  security. I installed Google Authenticator on my phone and started experimenting it with Java EE security.

I’ll describe what I did and some of the issues I found. You can get a copy of the source when you reach the end of the blog. Hopefully the journey is as pleasant as the destination 😉

The blog is broken into the following sections

  1. In the first part, I’ll give a brief intro to Google Authenticator/time based onetime password and how it works
  2. Setup JDBC realm for Glassfish application server in preparation for using it in our form based authentication
  3. Create the login and user registration with JSF Faces Flow
  4. Configure container managed security to use TOTP for login

Time-base One Time Password

So lets start with TOTP. We are all familiar with the concept of password. Password are typically static; they do not change over time; so if someone knows your password then your account is compromised.

In most operating system you can configured a password expiry policy where users are forced to change their password at regular intervals eg. 6 months. This password expiry policy gets around the above mention problem every 6 months. But the risk still exist.

TOPT takes the password expiry policy to the extreme by expiring your password at an even faster rate like say every 30 seconds! The problem here is that you cannot change your password every 30 seconds; so to make this work, TOTP uses an algorithm to change the password for us.

Very briefly this is how the TOTP works: you pass to the TOTP password generator algorithm a secret key and the current time; out pops your password; So if you call the TOTP password generator now and then again 30 seconds later (assuming that your window is 30 seconds) using the same secret key, you will get 2 completely different passwords.

To verify a password generated by TOTP algorithm, the TOTP password verification must know your secret key and the time that the TOTP password is generated. Since its a 30 second expiry period in our example, the password verifier will be able to validate the password as long as it is run inside the same 30 second window when the password is generated. All the wonderful math can be found here. TOTP is an IETF standard RFC6238.

Examples of TOTP can be found in online banking like DBS’s OTP where the account holders are issued dongles that implement the TOTP algorithm along with their unique secret keys embedded in the dongles. 

Another way of using TOTP is to augment it with your regular password as in Google’s 2-Step Verification (the focus of this blog). Google’s 2-Step Verification (G2SV) relies Google Authenticator which replaces the dongle in the above example. You have to first create an account. The Authenticator captures your TOTP details of this account by scanning a QR code. The QR code is created from the following 3 pieces of information

  1. A base 32 encoding of a 10 digit hexadecimal number. Eg the bytes from the string 8-polytope (“8-polytope”.getBytes()) will produce the following base 13 encoding: HAWXA33MPF2G64DF. This is the secret key and is unique to each user
  2. An issuer, typically the name of a company or the issuing authority eg. Yoyodyne Propulsion Systems
  3. A label, which I like to call the subject, is the user’s name or some identifier eg. johnwhorfin

All the information have to be encoded in the otpauth format. If we were to encode the values from above, we will get the following

otpauth://totp/Yoyodyne+Propulsion+Systems%3Ajohnwhorfin%3Fsecret%3DHAWXA33MPF2G64DF%26issuer%3DYoyodyne+Propulsion+Systems

Note that the otpauth string have to be URL safe. We can how use this otpauth to generate a QR using Google’s QR generator. You can find the query string parameters here. So to get the QR code of 350×350 pixel in size, enter the following

https://www.google.com/chart?chs=350x350&cht=qr&chl=otpauth://totp/Yoyodyne+Propulsion+Systems%3Ajohnwhorfin%3Fsecret%3DHAWXA33MPF2G64DF%26issuer%3DYoyodyne+Propulsion+Systems

See the QR in its full glory here.  Of course the entire process of generating the OTP can be automated.

Clearly the secret key in which the entire TOTP scheme hinges is the weakest link. If we minimize the exposure of the secret key (like keeping it from the user, using HTTPS to generate the QR) then we can potentially make using TOPT reasonably secure. Well get to that a little later.

 

Glassfish JDBC Realm

Java EE security realms is a place where username, password and groups are stored. When you configure security for your application you need to specify which security realm you application will be associated with. When a user login to your application, it is from this realm that the application will get the user’s credentials, password and group id. See here for more details.

A JDBC realm is a realm where usernames, passwords, etc are stored in a RDBMS. You first create the required tables in a RDBMS; next you create a connection pool on top of it followed by a JDBC resource.

Since I’m using Glassfish and Java DB/Apache Derby, I’ll be creating and configuring JDBC realm based on these. There are lots of blogs and tutorial on creating JDBC realm for Glassfish so I won’t go into it in great detail. I’ll just list out my configuration.

The following are the table schemas, based on Apache Derby, which I’ve copied from this blog. Adapt it to whatever database you’re using.

create table logintable (
   username varchar(128) NOT NULL CONSTRAINT usernamePK primary key,
   password varchar(128) NOT NULL,
   otp_secret varchar(128) NOT NULL
);

create table grouptable (
   username varchar(128) NOT NULL,
   groupid varchar(128) NOT NULL,
   CONSTRAINT GROUP_PK PRIMARY KEY(username, groupid),
   CONSTRAINT USER_FK FOREIGN KEY(username) REFERENCES logintable(username)
);

I’ve added an extra column in logintable called otp_secret. This column will be used to store our OTP secret key.

Create a JDBC resource to the database that has the above 2 tables; see here if you do not know how. Assume that the JDBC resource name is jdbc/security. Next define a JDBC realm for Glassfish with the following configuration according to the table schemas

Property Name Property Value
JAAS Context jdbc-realm (note: this is the realm’s name)
JNDI jdbc/security
User Table logintable
User Name Column username
Password Column password
Group Table grouptable
Group Table User Name Column username (note: this is the username column in grouptable)
Group Name Column groupid
Password Encryption Algorithm none (note: enter this value)
Digest Algorithm SHA-256 (note: this is important as you’ll need to know what message digest to use)

Login Flow for TOTP

The next thing that we’re going to do is to define a flow for a user to login or to register themselves. Registration is important here because this is where we generate the TOTP secret key. The following diagram shows the authentication and registration process

totp_flow

  • totp-login – this is the login page and the main entry point into this flow. The page need to have the same name as the flow name to indicate that this is the starting page
  • register – if we are a new user, then we click on a button on totp-login to get us here
  • qrcode – after we have successfully registered, the qrcode page will display the QR code for our TOTP which we have to use Google Authenticator to scan. After scanning, we return to totp-login to login
  • error – the error page

totp_flowI’ll be implementing the process using JSF using the new Faces Flows; so the files are created in a directory called totp-login; this incidentally will also be the name of the flow. 

Let start with the registration (register.xhtm) page. Note all JSF pages are at its bare minimum.

<h:form id="form">
   <h:inputText id="username" value="#{newUser.username}"/>
   <h:inputSecret id="password0" value="#{newUser.password}"/>
   <h:inputSecret id="password1" value="#{newUser.passwordAgain}"/>
   <h:commandButton value="Register Me" action="#{newUser.register()}"/>
</h:form>

And this is the backing bean for register.xhtml

@FlowScoped("totp-login")
@Named
public class NewUser {
   @EJB private UserManager mgr; 
   //JavaBean properties - not shown
   ...
   public String register() {
      //Checks
      ...
      Map<String, String> result = OneTimePassword.generateQR(username, Authentication.ISSUER);
      mgr.register(username, password, result.get(OneTimePassword.SECRET32));
      qrUrl = result.get(OneTimePassword.OTPAUTH);
      return ("qrcode");
   }
}

The code is quite simple. When the user clicks on Register Me button, we will call register(). After verify the fields, us use an EJB call UserManager to insert the username, password and the base32 encoded secret key into the JDBC realm. This corresponds to logintable above. It also assigns the user to a group called registered.

Recall that in our JDBC realm configuration, we say that we are using SHA-256 for the password digest; so in effect we don’t save the password but its digest. This hashing operation is performed by the register() method.

Before we register the new user into our database, we have to generate a secret key for it. We use generateQR(); generateQR() takes in 2 parameters, the subject and the issuer. In the example above, for the subject we will be using the user’s login name and the issuer is hardcoded to “Yoyodyne Propulsion Systems”. generateQR() returns a Map which hold a few entries. The name of the keys holds the following values

Key Name Value Description
secret32 Base32 encoded String of a randomly generated 10 byte secret
otpauth otpauth URI
url Full URL, with the otpauth, to the Google site that generates the QR code
secret Stringify 10 byte secret. Note in the demo, we only use the first 2 keys viz. secret32 and otpauth

We assign the otpauth URI to a property and move to the qrcode.xhtml page to display the QR code shown below

<h:outputText value="
   Please scan the QR code on the right using 
   your Google Authenticator App. After that you can 
   proceed to login."/>           
<h:commandButton value="Login" action="totp-login"/>
<h:graphicImage url="#{newUser.qrUrl}"/>     

A digression: I found that graphicImage cannot load images from an external source viz. if I pass it http://www.google.com/chart?… I’ll get a broken image. Not sure if this is a bug with JSF (Majorra) or its part of the specs. Anyways, this blog pointed out a possible workaround by proxying the image via a Servlet. The otpauth is appended to /qrcode. So the url attribute in graphicImage references /qrcode/the_otpauth instead of http://www.google.com/chart?… A Servlet is mapped to /qrcode/* to handle the image loading. See QRCodeServlet for more details.

All that remains now is the login page which is shown below

<h:form>
   <h:inputText id="username" value="#{authentication.username}"/>
   <h:inputSecret id="password" value="#{authentication.password}"/>
    <h:inputText id="otp" value="#{authentication.otpCode}"/>
   <h:commandButton value="New User" action="register"/>
   <h:commandButton value="Login" action="#{authentication.login()}"/>
</h:form>

and its corresponding backing bean

@FlowScoped("totp-login")
@Named
public class Authentication implements Serializable {
   //Getters and Setters not shown
   ...
   @EJB private UserManager mgr;
   public String login() {
      FacesContext ctx = FacesContext.getCurrentInstance();
      HttpServletRequest req = (HttpServletRequest)ctx.getExternalContext().getRequest();
      if (null != req.getRemoteUser())
         return ("protected/menu");
      if (null == req.getAuthType()) {
         ctx.addMessage(null, new FacesMessage("Not configured for container managed login"));
         return ("error");
      }
      try {
         req.login(username, password);
      } catch (ServletException ex) {
         ctx.addMessage(null, new FacesMessage("Incorrect login"));
         return ("error");
      }
      User user = mgr.getUser(username);
      if (null == user) {
         ctx.addMessage(null, new FacesMessage("Cannot find user."));
         return ("error");
      }
      try {
         if (!OneTimePassword.checkCode(user.getOtpSecret(), otpCode, System.currentTimeMillis())) {
            req.logout();
            ctx.addMessage(null, new FacesMessage("Incorrect login"));
            return ("error");
         }
      } catch (InvalidKeyException | NoSuchAlgorithmException | NumberFormatException ex) {
         ctx.addMessage(null, new FacesMessage("Exception: " + ex.getMessage()));
         return ("error");
      }
      return ("success");
   }
}

The login() method takes a user name and password and will throw a ServletException if the that combination is incorrect. It will also throw ServletException under 2 other circumstances

  • If the application have not been configured for security. We check that using getAuth() method or
  • If we attempt to login a use that have been authenticated. So we check if we have the user name by calling getRemoteUser(). If this is not null, meaning that this request has been authenticated, then we redirect the request into the secure area. More on secure area later.

After successfully login, we perform the 2nd validation by checking the OTP. We pass in the secret key, which is returned when we get the User from getUser(), the OTP password that the user entered and the current time to OneTimePassword.checkCode(). checkCode() method is  this algorithm that I found here. checkCode() verifies the password using a 120 second window viz. if you give it the current time, it’ll take the current, past and future 30 second slot. If we don’t do that then the time on the OTP generator (the mobile phone that runs Google Authenticator) and the time on the server that host the web application cannot be more than 30 seconds apart.

Note that if the TOTP password is incorrect we perform a logout (logout()) before returning; the other method that you can use is HttpSession.invalidate() which performs a logout as well. The difference is that logout() will not destroy the HttpSession. One reason you might not want to invalidate the session is to keep a count of failed attempts and disable the username for 5 minutes for example.

If the OTP is correct, we exit the TOTP authentication flow by returning the success token. I’m using explicit flow definition, so the definition is as follows

<flow-definition id="totp-login">
    <flow-return id="success">
        <from-outcome>/protected/menu</from-outcome>           
    </flow-return>
</flow-definition>

where everything under the /protected  directory requires authentication and authorization before access.

 

Form Based Authentication with TOTP

We’ll not turn our attention to configuring the web application with the totp-login flow described above using the JDBC realm that we have created. So if you want to secure an application in Java EE, you have to specify 3 things

  1. Authentication scheme – how are we going to collect username and password and to perform the authenticatoin
  2. Authorized role(s) – what are the application defined security roles
  3. Security constraint – what are the resources that needs to be authenticated and who is authorized to access it. The constraint ties authentication and authorization into a neat little package

Authentication Scheme

Let start the discussion by looking at authentication scheme. Java EE supports container managed security. If we’re dealing with securing web application, then common ways to authenticate a user are either basic or form-based (see here for other ways).

When you use basic authentication, you get a dialog box to request for your user name and password. If a correct combination of username and password is presented, then you will be redirected to the protected resource. Basic authentication does not give you control over the login page and the authentication process. Say you want to enter a third field, eg our TOTP field, as part of the the login, there is just no way of doing this.

Enter form based authentication. With form based authentication, you create a login and an error page in the web.xml file. The login page needs to contain j_username and j_password field; these values are POSTed to j_security_check. Form based authentication gives you control over the L&F of your login page but you’re still out of  the loop when it comes to the authentication process. Again it does not allow us to arbitrarily include any additional data into the authentication process. You can do it after you’ve passed security check but not during. Another common gripe with form based authentication is that its not very JSF friendly; you have to fallback to <form> and <input>.

So in Java EE 6, the Servlet API introduced programmatic security. At its heart, it is still form based authentication. However instead of relying of j_security_check to collect username and password, your application is now be responsible for collecting these information and performing the authentication. This opens up a lot of possibilities; you are no longer restricted to just username and password. You can include additional fields; see Authentication.login() and totp-login page above.

The following XML fragment shows form based authentication for our TOTP authentication

<login-config>
   <auth-method>FORM</auth-method> 
   <realm-name>jdbc-realm</realm-name> 
   <form-login-config>
      <form-login-page>/faces/index.xhtml</form-login-page> 
      <form-error-page>/faces/index.xhtml</form-error-page> 
   </form-login-config>
</login-config>

Note that in a typical form based login, you have 2 different views for login and for error. The container will redirect the request to the error page if you’ve login incorrectly. In our “semi” container managed security, our application controls this (see the flow above). So you don’t actually need an error page; I therefore set it to index.xhtml.

Another issue is cannot enter a JSF flow with a HTTP GET. In a typical form based login configuration, you provide the actual login page. In this scenario, we cannot have /faces/totp-login; this will not start the flow. What you need to do here is provide a view which have a POST back to start the totp-login flow; in this case index.xhtml looks like this

<h:form> 
   <h:commandLink action="totp-login" value="Login"/>
</h:form>

Authorized Roles

Once you’ve setup the authentication mechanism, you now need to define a security role. Our demo application have a single role which is authenticated.

<security-role>
    <description/>
    <role-name>authenticated</role-name>
</security-role>

The authenticated role needs to be mapped to users and groups from the jdbc-realm. This mapping is application server specific. Glassfish uses a file called glassfish-web.xml to perform this mapping. The following XML fragment shows how we map authenticated to registered group.

<security-role-mapping>
  <role-name>authenticated</role-name>
  <group-name>registered</group-name>
</security-role-mapping>

Security Constraints

A security constraint defines what resources are to be protected and what roles are allowed to authorized to access it. Lets say we wish to secure everything under the /protected path. So when we try to access /faces/protected/menu, the application server will require you to be authenticated first; then check if you are authorized with the role to access that resource. In our case our security constraints looks like this

<security-constraint>
    <display-name>Constraint1</display-name>
    <web-resource-collection>
        <web-resource-name>protected</web-resource-name>
        <description/>
        <url-pattern>/faces/protected/*</url-pattern>
    </web-resource-collection>
    <auth-constraint>
        <description/>
        <role-name>authenticated</role-name>
    </auth-constraint>
</security-constraint>

The above constraint says that what we are protecting is everything under the /faces/protected path; only those uses who are authenticated to the authenticated role will be authorized to access it.

 

Screenshots

The following are some screenshots with a running commentary of what’s happening

totp0

This is the login page. Assume that we are a new user, so we click on New User button. This brings us to the following screen

totp1

We enter the new user details. Then click on Register Me button. This will call NewUser.register(). A secret key will be generated and the appropriate QR code link is created based on the username, issuer and the secret key. The following page is displaying asking the user johnbigboote to scan that into his Google Authenticator.

totp2

After scanning in you should see the following in the Google Authenticator

jb

The number 732803 is the OTP and the ‘pie chart’ to the right is the 30 seconds count down timer. After login you’ll be redirected to the secure area.

totp3

You can use this approach to integrate other authentication mechanism like adding CAPTCHA.

I’ve listed a few caveats in the blog. They are mostly related to JSF and not security. So let me just state one here. One of the big advantage of container managed security is that if you are going to access /faces/protected/menu resource and your are not authentication, the application server will redirect you to the login page (if we’re using form based authentication). After you have authenticated and you’re authorized to access that resource, the container will now redirect you to that resources which is /faces/protected/menu in our case.

But using programmatic authentication, you lose this ability. I have to go to a specific view after a successful login. I cannot find a way of redirecting the request back to the original resource. Let me know if you have a standard and portable way of doing this.

The source code can be found here. It’s been an extremely long blog. Didn’t start off that way. Thanks for reading.

Till next time.

Advertisements

Marrying Socket.IO Client with Java EE 7’s WebSocket

just_marriedSocket.IO is a JavaScript library that uses a variety of transport (WebSocket, XHR polling, etc) to allow you to develop real-time web application. Socket.IO hides the underlying transport mechanism by figuring out what is the best transport to use between the browser and the server. It performs a handshake to determine this. Most Socket.IO servers are written in JavaScript running in node.js.

Java EE 7 was released recently. One of the key themes of EE 7 is better support for HTML 5 application. The support for modern web application started in Java EE 6 with the inclusion of RESTful web services. In EE 7, the powers that be added WebSocket and JSON.

I was recently learning and mucking around with Socket.IO. A thought struck me; can I connect a Socket.IO client to a Java EE server? I did a little investigation; I found 2 server side Java implementation of Socket.IO. They are netty-socketio and Socket.IO-Java. While both are excellent implementations and have been used in production, they are tied to specific servers. netty-socketio is based on Red Hat’s async IO framework called Netty and Socket.IO-Java runs on top of Jetty leveraging its WebSocket and continuation support.

I wanted something cross platform using standard API and  more importantly, for me at least, to use Java EE’s programming model (what I’m familiar with) and utilize its resources (JPA, EJB, etc).

The following is a sort of step by step guide on how you integrate a Socket.IO client to Java EE 7 using the new WebSocket API (JSR-356). What I’m doing here is based on looking at Socket.IO-Java sources.

Something Old

When a Socket.IO client connects, it does 2 things

  • Sends a HTTP GET to /socketio to download the socket.io.js JavaScript file. This is the result of <script>
  • Sends a second HTTP GET to /socketio/1/ for the handshake. The server should respond with a colon delimited string comprising of the session id, heart beat timeout, default timeout and a list of supported transport separated by comma. Eg 123456:15000:10000:websocket,xhr-polling

The easiest way to handle the handshake part is to use Servlet and that has to be mapped to /socket.io/*. Override doGet() and doPost() to handle the client request.

//Bare minimum, no error checking

@WebServlet(urlPatterns={“/socket.io/*”})

public class SocketIOServlet extends HttpServlet {

   private static final String CONFIG = “:15000:10000:websocket”;

   @Override

   protected void doGet(HttpServletRequest req, HttpServletResponse resp)

         throws IOException, ServletException {

      serve(req, resp);

   }

   @Override

   protected void doPost(HttpServletRequest req, HttpServletResponse resp)

         throws IOException, ServletException {

      serve(req, resp);

   }

   private void serve(HttpServletRequest req, HttpServletResponse resp)

         throws IOException, ServletException {

      String path = req.getPathInfo().trim();

      path = path.startsWith(“/”)? path.substring(1): path;

      String[] paths = path.split(“/”);

      //GET /socket.io/socket.io.js – returns the socket.io.js library

      if (“GET”.equals(req.getMethod()) && “socket.io.js”.equals(path[0])) {

         resp.setContentType(“text/javascript”);

         //Copy socket.io.js back to client, then return, not shown

        ….

      } else if (parts.length <= 2) {

         //Return handshake

         try (PrintWriter pw = resp.getWriter()) {

            pw.print(req.getSession().getId() + CONFIG);

         }

      }

   }

}

The important thing to note is that in the handshake back to the client, we indicated that we only support WebSocket. See CONFIG constant.

Something New

Once the handshake is successful, the client will now attempt to communicate with us using WebSocket. WebSocket is the newest API in the EE 7 stack. Its very similar, in terms of programming style, to JAX-RS. See here for a comprehensive WebSocket tutorial.

To communicate with the client (Socket.IO), the WebSocket end point must be mapped to /socket.io/protocol_version/websocket/sessionId eg /socket.io/1/websocket/123456. The session id is the variable part of the path so we need to create an endpoint with path parameter to match that but we will not be using it unless you are interested in specific clients.

@ServerEndpoint(value=”/socket.io/1/websocket/{sessionId}”)

public class SocketIOWebSocketHandler {

   @OnOpen

   public void open(Session session) {

      session.getBasicRemote().sendObject(connect());

   }

   @OnClose

   public void close(Session session) {

      session.getBasicRemote().sendObject(close());

      session.close();

   }

   @OnMessage

   public void message(Session session, String msg) {

      System.out.println(msg);

   }

}

So if you look at handling WebSocket transport with the client, its actually quite straightforward. The part that is might be a little confusing is why sendObject()? And what is in connect() and close(). Another question might be the format of msg.

 

Something Borrowed

Socket.IO defines a frame/packet based wire protocol that it uses for all communications between the 2 ends. A typical frame looks something like this

   3:::hello world

So if we were to receive some data from the client and print that out, we will see something similar to above.

As I’ve mentioned at the start of this blog, the code here is largely based on studying Socket.IO-Java sources. Instead of writing my own parser for the Socket.IO frames, I’m going to just use Socket.IO-Java’s. The class in question is SocketIOFrame. Its quite easy to use.

To parse a message from the client

List<SocketIOFrame> frames = SocketIOFrame.parse(msg);

SocketIOFrame has the following methods to identify the frame

  • getFrameType() – whether its a data frame or control frames
  • getMessageType() – text or JSON message
  • getData() – the data

So message() method above can be written as follows (we’ll only be covering the message)

@OnMessage

public void message(Session session, String msg) {

   for (SocketIOFrame f: SocketIOFrame.parse(msg))

      switch (f.getFrameType()) {

            …

         case EVENT:

         case JSON_MESSAGE:

         case MESSAGE:

      }

}

Lets look at how a Socket.IO client sends each of these message type and how we handle it on the server. Assume that socket is a Javascript Socket.IO socket.

Client

socket.send(“hello world”);

Server

case MESSAGE:

   System.out.println(f.getData());

Client

socket.json.send({message: “hello world”})

Server

case JSON_MESSAGE:

   StringReader data = new StringReader(f.getData());

   JsonReader reader = Json.createReader(data);

   JsonObject jsonData = reader.readObject();

   System.out.println(jsonData.getString(“message”));

Client

socket.emit(“greetings”, {message: “hello world”});

Server

case EVENT:

   StringReader data = new StringReader(f.getData());

   JsonReader reader = Json.createReader(data);

   JsonObject jsonData = reader.readObject();

   String eventName = jsonData.getString(“name”); // greetings

   JsonArray payload = jsonData.getJsonArray(“args”); // { … }

When you are sending data back to the client, the message have to be framed as well. Assume we have the following message handlers on the client

socket.on(“message”, function(data) {

   console.log(“MESSAGE: “ + data);

});

socket.on(“greetings”, function(data) {

   console.log(“greetings: “ + data);

});

The following shows how to send message to each of the above handlers

Sending as text

SocketIOFrame f = new SocketIOFrame(

      SocketIOFrame.FrameType.MESSAGE

      , SocketIOFrame.TEXT_MESSAGE_TYPE, message);

session.getBasicRemote().sendObject(f);

Sending as JSON

StringWriter w = new StringWriter();

try (JsonWriter writer = Json.createWriter()) {

   writer.write(json);

}

SocketIOFrame f = new SocketIOFrame(

      SocketIOFrame.FrameType.JSON_MESSAGE

      , SocketIOFrame.JSON_MESSAGE_TYPE, w.toString());

session.getBasicRemote().sendObject(f);

Sending as custom event

JsonArray array = Json.createArrayBuilder()

      .add(/* add data as JsonObject to array */).build();

JsonObject json = Json.createObjectBuilder()

      .add(“name”, “greetings”)

      .add(“args”, array).build();

StringWriter w = new StringWriter();

try (JsonWriter writer = Json.createWriter()) {

   writer.write(json);

}

SocketIOFrame f = new SocketIOFrame(

      SocketIOFrame.FrameType.EVENT

      , SocketIOFrame.JSON_MESSAGE_TYPE, w.toString());

session.getBasicRemote().sendObject(f);

A connect SocketIO frame (connect() method) is created like so

new SocketIOFrame(FrameType.CONNECT

      , SocketIOFrame.TEXT_MESSAGE_TYPE, “”)

and close SocketIO frame (close() method) is

new SocketIOFrame(FrameType.CLOSE

      , SocketIOFrame.TEXT_MESSAGE_TYPE, “server”)

Something Blue

The Socket.IO client makes the following assumption when communicating with the server

  • All communication endpoint are at socket.io viz. when you do io.connect(“http://myserver:8080”), it automatically look for the server side end point to be at http://myserver:8080/socket.io
  • All data send between the client and the server are enclosed in a frame defined by Socket.IO library
  • Following from the first point, the client always talks to the same end point. In the above example, all data are directed to http://myserver:8080/socket.io. So the handler must look at the data and dispatch to appropriate handlers. It would be nice if Socket.IO can use end points to discriminate the message handlers eg.  http://myserver:8080/socket.io/news

Lets address each of the above issues. When you deploy a Java EE web application, you always deploy it under an application context. Lets say the web application uses jsocketio as the application context, then you can use the resource option to remap the endpoint.

By default if you do the following

var socket = io.connect(“http://localhost:8080”);

socket.io will look for the end point at http://localhost:8080/socket.io as discussed above. To include the web application context, use the resource option

var socket = io.connect(“http://localhost:8080”

      { resource: “jsocketio/socket.io”});

See here for a detailed description of other options.

The workaround for SocketIO frames can handle using WebSocket’s encoders and decoders.

An incoming message first be passed to a decoder which converts the messages into a List<SocketIOFrame>. We then inject this list into the @OnMessage method. Note that in the above example we perform the decoding ourself. We can actually delegate this to decoders.

For outgoing messages, we have to create SocketIOFrame. Then use sendObject() to send it back to the client. The SocketIOFrame encoder will intercept this object and convert it to a frame.

public class SocketIOFrameDecoder

      implements Decoder.Text<List<SocketIOFrame>> {

   @Override

   public List<SocketIOFrame> decode(String value)

         throws DecodeException {

      return (SocketIOFrame.parse(value));

   }

   @Override

   public boolean willDecode(String value) { return (true); }

   …

}

public class SocketIOFrameEncoder

      implements Encoder.Text<SocketIOFrame> {

   @Override

   public String encode(SocketIOFrame frame)

         throws EncodeException {

      return (frame.encode());

   }

   …

}

We now need to associate the encoder/decoder pair with SocketIOWebSocketHandler

@ServerEndpoint(value=”/socket.io/1/websocket/{sessionId}”

      , encoders = { SocketIOFrameEncoder.class}

      , decoders = {SocketIOFrameDecoder.class}

)

public class SocketIOWebSocketHandler {

   @OnMessage

   public void message(Session session, List<SocketIOFrame> frames) {

      //Process inbound messages

      for (SocketIOFrame f: frames)

         //Do something

      …

      //Send reply back

      SocketIOFrame sioFrame = new SocketIOFrame(…);

      session.getBasicRemote().sendObject(sioFrame);

   }

You can find more details on encoders and decoders here.

The third and final caveat when working with a Socket.IO client is that all messages are directed at the same endpoint URL. If you look at how Socket.IO is used with node.js, you’ll notice that you can attach handlers to events. I’ve talked about this above so I won’t be repeating myself here. It would be nice to be able to map event names, in particular those send by using emit(), to WebSocket handlers.

 

I’m currently working on a simple framework so I’ll post the complete code when I complete the framework.

Everything that I’ve described I’ve tested with Chrome, Glassfish 4 and NetBeans 7.4 beta. For some strange reason, Socket.IO client on refuses to use WebSocket on Firefox.

Till next time.

%d bloggers like this: