Skip navigation.

CometD 2 Java Server Authorizers

CometD 2 Java Server API: Authorizers

Introduction

From CometD 2.1.0, a new feature has been implemented to allow fine-grained control of authorization on channel operations: authorizers.

Authorizers are objects that implement org.cometd.bayeux.server.Authorizer, and are added to server-side channels at setup time (before any operation on that channel may happen).

Authorizers are particularly suited to control authorization on those channels that are created at runtime, or for those channels whose id is built at runtime by concatenating strings that are unknown at application startup time (see examples below).
Authorizers do not apply to meta channels, but only to service channels and to normal (broadcast) channels.

Authorizers may be added to wildcard channels (such as /chat/*), and will impact all channels matched by the wildcard channel onto the authorizer has been added.
An authorizer added to /** impacts all (non-meta) channels.

For a non wildcard channel such as /chat/room/10, the authorizer set is the union of all authorizers on that channel and of all authorizers on wilcard channels that match the channel (in this case authorizers on channels /chat/room/*, /chat/room/**, /chat/** and /**).

Authorization Algorithm

Authorizers control access to channels in collaboration with the org.cometd.bayeux.server.SecurityPolicy that is currently installed.

The org.cometd.bayeux.server.SecurityPolicy class exposes three methods that can be used to control access to channels:

public boolean canCreate   (BayeuxServer server, ServerSession session, String channelId,      ServerMessage message);
public boolean canSubscribe(BayeuxServer server, ServerSession session, ServerChannel channel, ServerMessage message);
public boolean canPublish  (BayeuxServer server, ServerSession session, ServerChannel channel, ServerMessage message);

which control, respectively, the operation to create a channel, the operation to subscribe to a channel, and the operation to publish to a channel.

The complete algorithm for the authorization is the following:

  1. If there is a security policy, and the security policy denies the request, then the request is denied.
  2. Otherwise, if the authorizer set is empty, the request is granted.
  3. Otherwise, if at least one authorizer explicitly denies the operation, the request is denied and remaining authorizers are no consulted.
  4. Otherwise, if at least one authorizer explicitly grants the operation, the request is granted.
  5. Otherwise, as no authorizer has explicitly granted the operation, the request is denied.

The order in which the authorizers are called is not important.

Authorization is performed by implementing the method:

public Result authorize(Operation operation, ChannelId channel, ServerSession session, ServerMessage message);

There are three possible results that could be returned:

public Result authorize(Operation operation, ChannelId channel, ServerSession session, ServerMessage message);
{
    return Result.grant();
}

Result.grant() explicitly grants the permission to perform the operation passed in as parameter on the channel passed in as parameter.
There must be at least one authorizer that grants the operation, otherwise the operation is denied.
The fact that one (or multiple) authorizers grant an operation does not imply that the operation is granted at the end of the authorization algorithm: it could be denied by another authorizer in the authorizer set for that channel.

public Result authorize(Operation operation, ChannelId channel, ServerSession session, ServerMessage message);
{
    return Result.ignore();
}

Result.ignore() neither grants nor denies the permission to perform the operation passed in as parameter on the channel passed in as parameter.
Ignoring the authorization request is the usual way to not granting authorization if the conditions for which the operation must be granted do not apply.

public Result authorize(Operation operation, ChannelId channel, ServerSession session, ServerMessage message);
{
    return Result.deny("reason to deny");
}

Result.deny() explicitly denies the permission to perform the operation passed in as parameter on the channel passed in as parameter.
Denying the authorization request immediately result in the authorization being denied without even consulting other authorizers in the authorizer set for that channel.

Typically, denying authorizers are used for cross-cutting concerns: where you can have a sophisticated logic in authorizers to grant access to users for specific paid TV channels based on the user's contract (imagine that bronze, silver and gold contracts give access to different TV channels), you have a single authorizer that denies the operation if the user's balance is insufficient, no matter the contract or the TV channel being requested.

Example

The following example assume that the security policy does not interfere with the authorizers, and that the code is executed when the channel does not exist yet (either at application startup or in places where the application logic ensures that the channel has not been created yet).

Let's imagine an application that allows to watch and play games.

Typically, an ignoring authorizer is added on a root channel:

BayeuxServer bayeuxServer = ...;
bayeuxServer.createIfAbsent("/game/**");
ServerChannel gameStarStar = bayeuxServer.getChannel("/game/**");
gameStarStar.addAuthorizer(GrantAuthorizer.GRANT_NONE);

This ensures that the authorizer set is not empty, and that by default (if no other authorizer grants or deny) the authorization is ignored and hence denied.

Only captains can start a new game, and to do so they create a new channel for that game, for example /game/123 (where 123 is the gameId):

gameStarStar.addAuthorizer(new Authorizer()
{
    public Result authorize(Operation operation, ChannelId channel, ServerSession session, ServerMessage message)
    {
        boolean isGameChannel = !channel.isWild() && new ChannelId("/game").isParentOf(channel);
        if (operation == Operation.CREATE && isGameChannel)
        {
            if (isCaptain(session))
                return Result.grant();
            return Result.deny("Only captains can create game channels");
        } 
        return Result.ignore();
    }
});

Everyone can watch the game:

gameStarStar.addAuthorizer(GrantAuthorizer.GRANT_SUBSCRIBE);

Only players can play:

ServerChannel gameChannel = bayeuxServer.getChannel("/game/" + gameId);
gameChannel.addAuthorizer(new Authorizer()
{
    public Result authorize(Operation operation, ChannelId channel, ServerSession session, ServerMessage message)
    {
        if (operation == Operation.PUBLISH)
        {
            if(isPlayer(session,channel))
                return Result.grant();
            return Result.deny("Only players can publish to "+channel);
        } 
        return Result.ignore();
    }
});

The authorizers are the following:

/game/**  --> one authorizer that ignores everything
          --> one authorizer that grants captains to create games
          --> one authorizer that grants everyone to watch games
/game/123 --> one authorizer that grants players to play

Imagine that later you want to forbid to criminal supporters to watch games, so you can add another authorizer (instead of modifying the one that grants everyone to watch games):

gameStarStar.addAuthorizer(new Authorizer()
{
    public Result authorize(Operation operation, ChannelId channel, ServerSession session, ServerMessage message)
    {
        boolean isCriminalSupporter = isCriminalSupporter(session);
        if (operation == Operation.SUBSCRIBE && isCriminalSupporter)
            return Result.deny("criminal_supporter"); 
        return Result.ignore();
    }
});

The authorizers are now the following:

/game/**  --> one authorizer that ignores everything
          --> one authorizer that grants captains to create games
          --> one authorizer that grants everyone to watch games
          --> one authorizer that denies criminal supporters to watch games
/game/123 --> one authorizer that grants players to play

Note how authorizers on /game/** never grant Operation.PUBLISH, which is only granted by authorizers on specific game channels.
Also, the specific game channel does not need to grant Operation.SUBSCRIBE, because its authorizer ignores the subscribe operation that is therefore handled by authorizers on the /game/** channel.

Atomic Authorizer Registration

If channels are created while load is being offered to the server, it is important to create the channel and register the authorizers in an atomic operation, so that no messages are processed before the authorizers are registered. This can be achieved by using initialiazers:

bayeuxServer.createIfAbsent("/game/"+game_id, new ConfigurableServerChannel.Initializer()
{
    public void configureChannel(ConfigurableServerChannel channel)
    {
        channel.addAuthorizer(new PlayerAuthorizer());
    }
});