Authentication is a complex task, and can be achieved in many ways, and most often each way is peculiar to a particular project.
Below you can find few ideas on how you can perform authentication.
Authentication information could be passed by the CometD JavaScript client to the server via XMLHttpRequest.open(method, url, async, user, password), but this is currently not supported.
The main reason is that it will not work in the cross domain case, since XMLHttpRequest does not support cross domain authentication.
Authentication information can be passed by the CometD JavaScript client to the server via HTTP request headers in each CometD request, and verified by the server (for example via a servlet filter).
It's possible to specify such request headers using the requestHeaders configuration parameter as specified here.
This method is more complicated than previous ones, but gives more flexibility.
Authentication information can be passed by the CometD JavaScript client to the server during Bayeux handshake.
The CometD JavaScript client handshake() and init() methods take an extra object that's merged with the handshake message, for example:
cometd.configure({
url: 'http://localhost:8080/myapp/cometd'
});
// Register a listener to be notified of authentication success or failure
cometd.addListener('/meta/handshake', function(message)
{
var auth = message.ext && message.ext.authentication;
if (auth && auth.failed === true)
{
// Authentication failed, tell the user
window.alert('Authentication failed!');
}
});
var username = 'foo';
var password = 'bar';
// Send the authentication information
cometd.handshake({
ext: {
authentication: {
user: username,
credentials: password
}
}
});
The Bayeux specification suggests that authentication information is stored in the ext field of the handshake message (see here).
On the server, we need to configure the Bayeux object with a org.cometd.SecurityPolicy (to deny the handshake to clients that provide bad authentication information) and with an org.cometd.Extension to process the successful authentication.
It is possible to configure the Bayeux object using the same methods used to integrate Bayeux services (see also the Spring Integration).
For example, using a configuration servlet:
public class BayeuxInitializer extends GenericServlet
{
@Override
public void init() throws ServletException
{
Bayeux bayeux = (Bayeux)getServletContext().getAttribute(Bayeux.ATTRIBUTE);
BayeuxAuthenticator authenticator = new BayeuxAuthenticator();
bayeux.setSecurityPolicy(authenticator);
bayeux.addExtension(authenticator);
}
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException
{
throw new ServletException();
}
}
Below you can find the code for the BayeuxAuthenticator class:
public class BayeuxAuthenticator extends AbstractBayeux.DefaultPolicy implements Extension, RemoveListener (1)
{
@Override
public boolean canHandshake(Message message) (2)
{
Map<String, Object> ext = message.getExt(false);
if (ext == null)
return false;
// Be sure the client does not cheat us
ext.remove("authenticationData");
Map<String, Object> authentication = (Map<String, Object>)ext.get("authentication");
if (authentication == null)
return false;
Object authenticationData = verify(authentication); (3)
if (authenticationData == null)
return false;
// Store the authentication result in the message for later processing
ext.put("authenticationData", authenticationData); (4)
return true;
}
public Message sendMeta(Client remote, Message responseMessage) (5)
{
if (Bayeux.META_HANDSHAKE.equals(responseMessage.getChannel())) (6)
{
Message requestMessage = responseMessage.getAssociated(); (7)
Map<String, Object> requestExt = requestMessage.getExt(false);
Object authenticationData = requestExt.get("authenticationData");
if (authenticationData != null)
{
// Authentication successful
// Link authentication data to the remote client (8)
// Be notified when the remote client disappears
remote.addListener(this); (9)
}
else
{
// Authentication failed
// Add extra fields to the response (10)
Map<String, Object> responseExt = responseMessage.getExt(true);
Map<String, Object> authentication = new HashMap<String, Object>();
responseExt.put("authentication", authentication);
authentication.put("failed", true);
// Tell the client to stop any further attempt to handshake (11)
Map<String, Object> advice = new HashMap<String, Object>();
advice.put(Bayeux.RECONNECT_FIELD, Bayeux.NONE_RESPONSE);
responseMessage.put(Bayeux.ADVICE_FIELD, advice);
}
}
return responseMessage;
}
public void removed(String clientId, boolean expired) (12)
{
// Unlink authentication data from the remote client (13)
}
// Other methods omitted
}
Note that:
BayeuxAuthenticator be a SecurityPolicy, an Extension and a RemoveListener, since the code is really tied togethercanHandshake() (a SecurityPolicy method), where we extract the authentication information from the message sent by the clientsendMeta() (an Extension method), which is called just after canHandshake() and just before sending the handshake response to the remote clientsendMeta(), and we further retrieve the server-side authentication data set in (4)removed() (a RemoveListener method), which is called when the remote client disappears, either because it disconnected or because it crashedThe reason for all this machinery is that at the moment of the invocation of canHandshake() the org.cometd.Client (server-side) object representing the remote client has not been created yet; it is only created if the handshake is allowed.
The most important steps are the number 8 and the number 13, where the server-side authentication data is linked/unlinked to/from the org.cometd.Client object.
This linking depends very much from project to project.
It may link a database primary key (of the row representing the user account) with the remote client id (obtained with remote.getId()), and/or viceversa, or it may link OAUTH tokens with the remote client id, etc.
Each Bayeux message always come with a client id, which can be thought as similar to the Http session id.
In the same way it is widespread practice to put the server-side authentication data in the HttpSession object (identified by the Http session id), in CometD web applications we can put server-side authentication data in a data structure where it can be later retrieved by Bayeux client id.
This data structure representing the "session" of a Bayeux client is not (yet) offered by the CometD API, and must be therefore coded by the CometD web application developer.
The Bayeux client ids are long, randomly generated numbers, exactly like Http session ids, and offer the same level security offered by a Http session id.
If an attacker manages to sniff a Bayeux client id, it can impersonate that Bayeux Client exactly in the same way it can sniff a Http session id and impersonate that Http session.
And, of course, the same solutions to this problem used to secure Http applications can be used to secure CometD web applications.