Automatically subscribe to a user-specific physical queue

You have a publisher which is sending to individual message queues for individual users. And you want each client to receive their messages such that:

  • Clients don’t need to know the name of their specific queue
  • Clients receive only their messages and cannot get the messages of others

This is shown in the following figure (click image to enlarge):

vq-arch-1

This tutorial takes you through the steps to configure the scenario shown above.

Prerequisites

  • Kaazing WebSocket Gateway requires a minimum of Java 1.8
  • This tutorial uses Maven to build some artifacts

Instructions

Download the Enterprise Edition of Kaazing WebSocket Gateway (KWG) and unpack it. Let’s refer to this location as GATEWAY_HOME

Start the gateway.

Linux: $ GATEWAY_HOME/bin/gateway.start --broker jms
Windows: > GATEWAY_HOME/bin/gateway.start.bat --broker jms

That command will both start the gateway and an ActiveMQ JMS broker that was included in the download for convenience.

We’ll use the JMS demo app in this tutorial. It is available from Github, and you can get it by cloning the repo:

$ git clone https://github.com/kaazing/javascript.client.tutorials.git
$ cd javascript.client.tutorials/jms

Open the JMS demo app in a browser by opening javascript.client.tutorials/jms/index.html:

jms-demo-app-1

Change the Location field to connect to the JMS service in the gateway you started: ws://localhost:8000/jms. Then press the Connect button:

jms-demo-app-2

Once connected, press the Subscribe button, then press the Send. You will see the message that was sent (in green) and then received (in blue):

jms-demo-app-3

This verifies that a message was sent from the browser, through the gateway, to the broker, and then back through the gateway and to the browser who had subscribed. Now you know all components are up and running successfully.

Press the Close button to end the connection.

Now that you’ve got the basic infrastructure working, let’s add the security elements. These have been prepared for you. You can get them from Github and build the artifacts using Maven:

$ git clone https://github.com/kaazing/blog-virtual-queues.git
$ cd blog-virtual-queues.git/security-demo
$ mvn clean install

Feel free to look through the project. I’ll explain each component as we use them.

You can import pom.xml into development environments like Eclipse if they are Maven-aware to make it easy to see the code and make changes.

The first step is to enable authorization in the gateway. This will let KWG know who the user is.

Open the gateway configuration file in a text editor (where GATEWAY_HOME is the location you installed KWG back in ):

GATEWAY_HOME/conf/gateway-config.xml

Replace your configuration with the following:

<?xml version="1.0" encoding="UTF-8" ?>
<gateway-config xmlns="http://xmlns.kaazing.org/2015/11/gateway">

  <properties>

    <property>
      <name>gateway.hostname</name>
      <value>localhost</value>
    </property>

    <property>
      <name>gateway.port</name>
      <value>8000</value>
    </property>

  </properties>

  <service>
    <name>JMS Service</name>
    <accept>ws://${gateway.hostname}:${gateway.port}/jms</accept>

    <type>jms</type>

    <properties>
      <!-- Settings for connecting to your JMS broker -->
      <connection.factory.name>ConnectionFactory</connection.factory.name>
      <context.lookup.topic.format>dynamicTopics/%s</context.lookup.topic.format>
      <context.lookup.queue.format>dynamicQueues/%s</context.lookup.queue.format>
      <env.java.naming.factory.initial>org.apache.activemq.jndi.ActiveMQInitialContextFactory</env.java.naming.factory.initial>
      <env.java.naming.provider.url>tcp://${gateway.hostname}:61616</env.java.naming.provider.url>
    </properties>

    <realm-name>security-demo</realm-name>

    <authorization-constraint>
      <require-role>customer</require-role>
    </authorization-constraint>

    <!-- Restrict cross site constraints before running in production -->
    <cross-site-constraint>
      <!-- * is not secure for production javascript applications -->
      <allow-origin>*</allow-origin>
    </cross-site-constraint>
  </service>

  <security>

    <realm>
      <name>security-demo</name>
      <description>Example realm with a simple token</description>

      <authentication>

        <http-challenge-scheme>Application Token</http-challenge-scheme>
        <http-query-parameter>mytoken</http-query-parameter>
        <authorization-timeout>3600 seconds</authorization-timeout>

        <login-modules>

          <login-module>
            <type>class:com.kaazing.demo.security.loginmodules.SimpleLoginModule</type>
            <success>required</success>
          </login-module>

        </login-modules>

      </authentication>

    </realm>

  </security>

</gateway-config>

The configuration, above, contains just one JMS <service> and one <realm>.

The <realm-name> in the service has a value of “security-demo“. This corresponds to the <realm>.<name> element in the <security> section.

Also in the service is an <authorization-constraint>. By adding an authorization constraint to the service, you are marking the service as requiring authorization, specifically that you need the “customer” role. And the realm tells the service how to enforce that authorization.

Down in the realm definition in the <security> section, it is configured to require a token. Normally tokens would arrive in the HTTP Authorization header, however the gateway can accept tokens in a variety of locations. Since this is a demo and not a production system, we will simplify things by letting the client pass a token as a HTTP query parameter called “mytoken“. You can see this in the two elements <http-challenge-scheme> and <http-query-parameter>.

Finally, you need to stipulate how to validate the token. Kaazing WebSocket Gateway uses standard JAAS for authorization, so a login module is specified in <login-modules> section.

A custom class called com.kaazing.demo.security.loginmodules.SimpleLoginModule was specified. Where is that class? The source code is in the repo you cloned back in step . You’ll find the login module here:

blog-virtual-queues/security-demo/src/main/java/com/kaazing/demo/security/loginmodules/SimpleLoginModule.java

Go to the terminal window where you started the gateway in . Press Ctrl-C to kill the process, then restart KWG using the same command in .

You should get an error like this:

ERROR Gateway failed to launch
java.lang.IllegalArgumentException:
Unable to find the login module class: com.kaazing.demo.security.loginmodules.SimpleLoginModule

That’s because we configured the gateway to use our SimpleLoginModule, but didn’t provide it at runtime.

Back in you built the security-demo project using Maven. (The command was mvn clean install in the directory that contains the pom.xml file.) That created the artifacts in the target directory.

Copy the jar file in blog-virtual-queues/security-demo/target to the lib directory in GATEWAY_HOME. For example:

$ cd blog-virtual-queues/security-demo
$ mvn clean install
$ cp target/security-demo-1.0-SNAPSHOT.jar GATEWAY_HOME/lib

(Making sure to replace GATEWAY_HOME with the path where you installed KWG, of course.)

Now restart the gateway. e.g.:

$ GATEWAY_HOME/bin/gateway.start --broker jms

This time the gateway will start successfully.

Before we go ahead and run the demo, there is one more change we need to make. The login module will write output to the log. For convenience, it’s nice to see the output in the console window where you are running the gateway.

Using a text editor, open GATEWAY_HOME/conf/log4j-config.xml.

Add an appender after the other appenders already in the file:

<appender name="STDOUT-loginmodule" class="org.apache.log4j.ConsoleAppender">
  <layout class="org.apache.log4j.PatternLayout">
    <param name="ConversionPattern" value="[LM] %-5p %m%n"/>
  </layout>
</appender>

Note that the ConversionPattern value begins with [LM]. That will help us identify the log output later.

Further down, add a logger for our login module class (notice how the name corresponds to the package name and class name of the login module):

<logger name="com.kaazing.demo.security.loginmodules.SimpleLoginModule">
  <level value="trace"/>
  <appender-ref ref="STDOUT-loginmodule"/>
</logger>

Save the file.

In the terminal window where the gateway is running, press Ctrl-C and restart the gateway. There will be no difference in the output right away, but you’ll see it once you connect.

Return to the browser where you had the client app running back in .

Since we just configured the gateway to accept a token, we now need to provide it. Normally in a production system, the client would be configured to retrieve the token from a token provider, after first authenticating. In this case, for simplicity, we will simply hardcode a token. This is useful to speed up development.

Change the Location field to:

ws://localhost:8000/jms?mytoken=%7B%22userid%22%3A%22joe%22%2C%20%22role%22%3A%22customer%22%7D

Re-written without the escape codes, you can see the token is a simple JSON construct:

ws://localhost:8000/jms?mytoken={"userid":"joe", "role":"customer"}

(Note that each time you refresh the page you’ll need to change the Location field again because the default location is different.)

Notice that the query parameter name of mytoken corresponds to the <http-query-parameter>mytoken</http-query-parameter> setting in the gateway configuration that you set in .

Press the Connect button. Once connected, press the Subscribe button, then Send to verify you can still subscribe and receive messages.

jms-demo-app-4

Look at the output in the terminal window where the gateway is running. You should see the following:

[LM] DEBUG initialize()
[LM] DEBUG Options:
[LM] DEBUG   GATEWAY_CONFIG_DIRECTORY=/ee-gateway/conf
[LM] DEBUG login()
[LM] DEBUG Obtained AuthenticationToken: [ scheme=Token {'mytoken'->'{"userid":"joe","role":"customer"}'} ]
[LM] DEBUG Extracted token data: [{"userid":"joe","role":"customer"}]
[LM] DEBUG Token validation successful
[LM] DEBUG commit()
[LM] DEBUG User principal added: joe
[LM] DEBUG User granted role: customer
[LM] DEBUG Principals added to Subject:
[LM] DEBUG   Principal: joe [com.kaazing.demo.security.loginmodules.MyUserPrincipal]
[LM] DEBUG   Principal: customer [com.kaazing.demo.security.loginmodules.MyRolePrincipal]

The [LM] prefix (short for “login module”) was specified by the log appender back in . This makes it easy to identify which component is logging the output.

Since the gateway uses JAAS for authorization, each client has a Subject (see javadoc). The Subject contains zero or more Principal objects (see javadoc) which simply hold a value that can interpreted any way you like.

In a typical scenario, the Subject will contain a Principal for the userid, another Principal for their role – or multiple Principal objects for multiple roles – and any other information you wish to store about the client or user.

From the output above, you can see that there is one Principal of type MyUserPrincipal with the value of “joe“, and a second Principal of type MyRolePrincipal with the value of “customer“. They have both been added to the Subject.

This identity information – the userid and role – is known to the gateway in a trusted way because it was derived from the token. In this example, the token was hardcoded in the URL and could be manipulated by a user. But in a real production system, the token would be tamperproof since it’s encrypted by a private key, and decrypted by the gateway using a public key. Or the gateway can query the security system with the token to verify it.

Thus the gateway ends up with the identity of the user in a trusted way, without a malicious client being able to influence it.

You can see the source code for SimpleLoginModule, MyUserPrincipal, and MyRolePrincipal in blog-virtual-queues.git/security-demo.

Now let’s set up the virtual queue. Open the gateway configuration file using a text editor: GATEWAY_HOME/conf/gateway-config.xml. Find the <properties> section of the JMS service and add the <queue> element shown below:

<properties>
  ...
  <queue>
    <name>public-api-messages</name>
    <virtual.name.format>private-api-messages_%s</virtual.name.format>
    <virtual.name.resolver>com.kaazing.demo.security.virtualqueues.VirtualQueueUseridResolver</virtual.name.resolver>
  </queue>

</properties>

The <name> element is the name of the queue the clients subscribe to: public-api-messages

The <virtual.name.format> element specifies the physical name of the queue that the gateway subscribes to on behalf of clients. The %s will be substituted with the result from VirtualQueueUseridResolver, which is the resolver specified in <virtual.name.resolver>.

Note that this example shows that the physical queue name (private-api-messages_USERID) can be different from the queue name used by clients (public-api-messages).

The resolver VirtualQueueUseridResolver is a Java class that has access to the Subject (and therefore all of the Principals) and can determine the value to be substituted into %s.

Save the configuration.

Now that you specified the VirtualQueueUseridResolver class in the configuration, you need to make it available to the gateway at runtime. Luckily, this was already done when you put the jar file in the lib directory back in , so you don’t need to repeat that.

But let’s update the log configuration so we can see the output. Edit GATEWAY_HOME/conf/log4j-config.xml in a text editor.

Add an appender after the other appenders already in the file:

<appender name="STDOUT-virtualqueue" class="org.apache.log4j.ConsoleAppender">
  <layout class="org.apache.log4j.PatternLayout">
    <param name="ConversionPattern" value="[VQ] %-5p %m%n"/>
  </layout>
</appender>

Note that the ConversionPattern value begins with [VQ] (for Virtual Queue). That will help us identify the log output later.

Further down, add a logger for our login module class (notice how the name corresponds to the package name and class name of VirtualQueueUseridResolver):

<logger name="com.kaazing.demo.security.virtualqueues.VirtualQueueUseridResolver">
  <level value="trace"/>
  <appender-ref ref="STDOUT-virtualqueue"/>
</logger>

Save the file.

In the terminal window running the gateway, press Ctrl-C to kill the gateway and then restart it for the changes to take effect.

Once the gateway was restarted, open the JMS demo app in the browser again. Remember to change the location to:

ws://localhost:8000/jms?mytoken=%7B%22userid%22%3A%22joe%22%2C%20%22role%22%3A%22customer%22%7D

Then press Connect. Once connected, change the Destination field to /queue/public-api-messages and press Subscribe. (The /queue/ notation informs the client that this is a queue. It is not part of the destination being subscribed to.)

Look at the KWG console output and you’ll see a message logged by the VirtualQueueUseridResolver class:

[VQ] DEBUG Resolved virtual queue userid: joe

Even though the client subscribed to the queue public-api-messages, because of the virtual queue configuration done earlier, the gateway will have actually subscribed to private-api-messages_joe.

Now let’s testing publishing a message and see if the user “joe” receives it. Since we don’t have a publisher, we’ll fake it by connecting as another user who will send a message.

Leave the existing browser tab open and keep Joe connected. Open a new browser tab to the same client app. If you’ve forgotten how, you did this back in .

This time, set the Location field to a user named “max”:

ws://localhost:8000/jms?mytoken=%7B%22userid%22%3A%22max%22%2C%20%22role%22%3A%22customer%22%7D

Press Connect.

Change the Destination field to /queue/private-api-messages_joe. (Remember, the /queue/ notation is to tell the client the destination type. The queue name as far as the backend broker is concerned is private-api-messages_joe.)

You don’t need to subscribe, since this user is only sending.

Change the Message field to any message you care to send, or leave the default message. Then press the Send button.

Switch back to the first browser tab with Joe, and you’ll see that he has received the message:

jms-demo-app-5

Let’s summarize what happened so far. User Joe subscribed to a queue called public-api-messages. That is not a real queue, but a virtual queue created by Kaazing WebSocket Gateway. KWG mapped that virtual queue to the physical queue called private-api-messages_joe.

The client did not need to know the name of the physical queue. The gateway did not rely on the client to provide the userid (since the client is outside the trust zone) but instead extracted that information in a trusted way from the token.

All clients can subscribe to the same virtual queue name, public-api-messages, and they will be correctly hooked up their corresponding physical queue, which may have a different name.

Finally, the publish can publish to the appropriate queue for any user it needs to, using the internal queue name. The publisher does not need to have any awareness of the publicly exposed queue name.

You can look at the source code of VirtualQueueUseridResolver in the security-demo folder of the blog-virtual-queues repo to see what it’s doing:

src/main/java/com/kaazing/demo/security/virtualqueues/VirtualQueueUseridResolver.java

The apply() method receives the Subject for the associated connection. Earlier when the user first connected, the login module fired and added two Principals to the subject: one for userid and one for the role.

The apply() method extracts the userid principal and returns its value. This is the value that’s substituted into the %s of the queue name specified in the gateway configuration earlier:

<virtual.name.format>private-api-messages_%s</virtual.name.format>

This will result in a subscription by KWG to the queue private-api-messages_joe.

We’re almost done, but there is still one last step. Right now, nothing is preventing a user from subscribing to another user’s queue. For example, user Max could subscribe to private-api-messages_joe and receive Joe’s messages.

KWG has fine-grained authorization for JMS operations to stop users taking unauthorized actions. These are enforced using a JmsAuthorizationFactory and JmsAuthorization (see API doc).

One has already been written for you so let’s add it to the gateway configuration. Open the gateway configuration file in a text editor: GATEWAY_HOME/conf/gateway-config.xml

In the <properties> section of the JMS service add an <authorization.factory> element, as shown in highlighted row below:

<service>

  ...

  <type>jms</type>

  <properties>

    ...

    <queue>
      <name>public-api-messages</name>
      <virtual.name.format>private-api-messages_%s</virtual.name.format>
      <virtual.name.resolver>com.kaazing.demo.security.virtualqueues.VirtualQueueUseridResolver</virtual.name.resolver>
    </queue>

    <authorization.factory>com.kaazing.demo.security.jmsauth.VirtualQueueOnlyAuthorizationFactory</authorization.factory>

  </properties>

  ...

</service>

As you can see, we set the authorization factory to use the VirtualQueueOnlyAuthorizationFactory class.

Save the configuration.

Since you added a new class to the configuration, it needs to be available to the gateway at runtime. As before, this was already done when you put the jar file in the lib directory back in , so you don’t need to repeat that step.

But let’s update the log configuration so we can see the output from this new class. Edit GATEWAY_HOME/conf/log4j-config.xml in a text editor.

Add an appender after the other appenders already in the file:

<appender name="STDOUT-jmsauth" class="org.apache.log4j.ConsoleAppender">
  <layout class="org.apache.log4j.PatternLayout">
    <param name="ConversionPattern" value="[JA] %-5p %m%n"/>
  </layout>
</appender>

Note that the ConversionPattern value begins with [JA] (for JMS Authorization). That will help us identify the log output later.

Further down, add a logger for our login module class (notice how the name corresponds to the package name and class name of VirtualQueueUseridResolver):

<logger name="com.kaazing.demo.security.jmsauth.VirtualQueueOnlyAuthorization">
  <level value="trace"/>
  <appender-ref ref="STDOUT-jmsauth"/>
</logger>

Save the file and restart the gateway from the command line. Now let’s see JMS authorization in action.

Go back to the first browser tab where the user is Joe. Refresh the page or reconnect. Remember to use the location:

ws://localhost:8000/jms?mytoken=%7B%22userid%22%3A%22joe%22%2C%20%22role%22%3A%22customer%22%7D

Press Connect.

Change the Destination field to /queue/public-api-messages, then press Subscribe.

Open the second browser tab for the user Max. Refresh the page or reconnect. Remember to use the location:

ws://localhost:8000/jms?mytoken=%7B%22userid%22%3A%22max%22%2C%20%22role%22%3A%22customer%22%7D

Press Connect.

Set the Destination field to /queue/private-api-messages_joe, then press Send. Check that the user Joe received the message in the other browser tab.

Return to the browser tab for Max. Now this time press the Subscribe button to attempt to subscribe to Joe’s queue. Your connection will be terminated.

Look at the KWG console output on the command line:

[JA] DEBUG canSubscribeToQueue() Queue kind: QUEUE, Queue name: private-api-messages_joe. Selector: null
[JA] DEBUG queueUserid: joe, Subject userid: max
[JA] DEBUG User max is illegally trying to subscribe to queue private-api-messages_joe. Closing connection
[LM] DEBUG logout()
[LM] DEBUG   Principal: max [com.kaazing.demo.security.loginmodules.MyUserPrincipal]
[LM] DEBUG   Principal: customer [com.kaazing.demo.security.loginmodules.MyRolePrincipal]

The lines with a prefix of [JA] are written by the JMS authorization factory that you just specified in the configuration.

The lines with a prefix of [LM] are written by the login module which you configured earlier.

As you can see from the third line, the JMS authorization factory is not allowing Max to subscribe to Joe’s queue. If an illegal operation is performed, the connection is terminated. This is to prevent malicious users from probing.

You can also see that when the connection was killed, the login module’s logout() method was invoked. This is an optional hook to let you do any clean up that may be necessary in your environment.

You can see the source code for the JMS authorization in the security-demo folder of the blog-virtual-queues repo.

src/main/java/com/kaazing/demo/security/jmsauth/VirtualQueueOnlyAuthorizationFactory.java
src/main/java/com/kaazing/demo/security/jmsauth/VirtualQueueOnlyAuthorization.java

Similarly to the virtual queue user resolver, the JMS authorization takes a Subject and extracts the user Principal. Each JMS operation has a canOPERATION() method in JMSAuthorization that lets you return true or false if the operation is permitted. Each method also receives parameters about the operation the user is performing.

In this example, we simply return true for all methods to allow them all. The exception is canSubscribeToQueue() which is being showcased in this tutorial.

canSubscribeToQueue() has a parameter for, among other things, the name of the queue the client is attempting to subscribe to. The object also has the Subject and thus knows who the client actually is.

When Max attempted to subscribe to the private-api-messages_joe queue, the canSubscribeToQueue() method compared the userid “max” with “joe“, which failed. The method returned false, which resulted in Max’s connection being terminated.

You’ve now completed all the steps.

With the set up described here, your publisher can publish to individual queues using some discriminator in the queue name. In this tutorial, we discriminated on the user id. So the publisher will publish to private-api-messages_joe, private-api-messages_max, etc.

Clients need only subscribe to a generic queue name, like public-api-messages. And it can be different than the physical queue being used by the publisher.

You can add a login module in the gateway which extracts user information in a trusted way from the token and attaches it to the client by putting Principals in the Subject.

You supply a virtual queue resolver which tells KWG which physical queue to subscribe to, based on credential information previously stored in the Subject. This ensures that when a user subscribes, they subscribe to their associated queue.

Finally, to prevent users deliberately trying to subscribe to queues they’re not allowed to, you can specify a JMSAuthorizationFactory.

The following diagram gives a quick overview of some of the key points shown in this article:

vq-arch-2

This tutorial contained a lot, bringing together various moving parts in Kaazing WebSocket Gateway that work with each other provide a flexible architecture to meet your needs.