JMS Gateway

Overview

The following document outlines the JMS approach in jWebSocket, the possibilities and options as well the respective implementation efforts.
The recent version majorly consists of:

  • The JMSEndpoint Library
  • A new JMSServer demo and JMSClient demo
  • The required low level jars and
  • Two configuration files to support the various SSO/OAuth environments in our infrastructure.
Demo Code

JMS Server Demo (to simulate and test responses)

The JMSServer Demo you will find in SVN at: [SVN]\jWebSocketDev\branches\jWebSocket-1.0\jWebSocketJMSGateway\jWebSocketJMSServer

This is the list of all required jars to run the server:

  1. activemq-all-5.8.0.jar
  2. commons-codec-1.8.jar
  3. commons-compress-1.5.jar
  4. commons-configuration-1.10.jar
  5. commons-lang-2.6.jar
  6. commons-lang3-3.1.jar
  7. commons-logging-1.1.3.jar
  8. httpclient-4.3.3.jar
  9. httpcore-4.3.2.jar
  10. jackson-annotations-2.4.3.jar
  11. jackson-core-2.4.3.jar
  12. jackson-databind-2.4.3.jar
  13. javolution-5.5.1.jar
  14. json-2-RELEASE65.jar
  15. jWebSocketCommon-1.0.jar
  16. jWebSocketJMSEndPoint-1.0.jar
  17. jWebSocketJMSServer-1.0.jar
  18. jWebSocketLDAP-1.0.jar
  19. jWebSocketSSO-1.0.jar
  20. log4j-1.2.17.jar
  21. spring-core-3.1.4.RELEASE.jar

To easily start the JMS server there is a batch and to easily maintain the configuration parameters there is a properties file available, these batch files can be found in [SVN]\jWebSocketDev\rte\jWebSocket-1.0\bin\ and [SVN]\jWebSocketDev\rte\jWebSocket-1.0\conf\JMSPlugIn\ respectively:

  • jWebSocketJMSServer.bat
  • JMSServer.properties

Of course, this is only a demo, in your application you can load the configuration parameters from your individual configuration resources/repository. Possibly some of the above jars are already embedded in your application environment. In this case, of course you only need to add the additional ones. To avoid any kind of redundancy or naming or version conflicts with this update we provide all jars totally unbundled.

JMS Client Demo (to simulate and test client requests)

The JMSClient Demo you will find in Perforce at: [SVN]\jWebSocketDev\branches\jWebSocket-1.0\jWebSocketJMSGateway\jWebSocketJMSClient
This is the list of all required jars to run the client:

  1. activemq-all-5.8.0.jar
  2. commons-codec-1.8.jar
  3. commons-compress-1.5.jar
  4. commons-configuration-1.10.jar
  5. commons-lang-2.6.jar
  6. commons-lang3-3.1.jar
  7. commons-logging-1.1.3.jar
  8. httpclient-4.3.3.jar
  9. httpcore-4.3.2.jar
  10. jackson-annotations-2.4.3.jar
  11. jackson-core-2.4.3.jar
  12. jackson-databind-2.4.3.jar
  13. javolution-5.5.1.jar
  14. json-2-RELEASE65.jar
  15. jWebSocketCommon-1.0.jar
  16. jWebSocketJMSClient-1.0.jar
  17. jWebSocketJMSEndPoint-1.0.jar
  18. jWebSocketLDAP-1.0.jar
  19. jWebSocketSSO-1.0.jar
  20. log4j-1.2.17.jar
  21. spring-core-3.1.4.RELEASE.jar

To easily start the JMS client there is a batch and to easily maintain the configuration parameters there is a properties file available in [SVN]\jWebSocketDev\rte\jWebSocket-1.0\bin\ and [SVN]\jWebSocketDev\rte\jWebSocket-1.0\conf\JMSPlugIn\ respectively:.

  • jWebSocketJMSClient.bat
  • JMSClient.properties

During the development you can optionally uncomment JMSLogging.setFullTextLogging(true); to log the tokens including all secrets and passwords. This is for debugging purposes only. Please ensure to comment this line, to not log any credentials or secrets in any live environment!

// only for debug purposes
// JMSLogging.setFullTextLogging(true);
Use Cases

Instantiating a new JWSEndPointClient:

To instantiate a new JWSEndPointClient please use the getInstance() method to allow you to capture potential exceptions, e.g. caused by duplicate endpoint ids:

// instantiate a new jWebSocket JMS Gateway Client
try {
   lJWSEndPoint = JWSEndPoint.getInstance(
      lBrokerURL,
      lGatewayTopic, // gateway topic
      lGatewayId, // gateway endpoint id
      lEndPointId, // unique node id
      5, // thread pool size, messages being processed concurrently
      JMSEndPoint.TEMPORARY // durable (for servers) or temporary (for clients)
   );
} catch (JMSException lEx) {
   mLog.fatal("JMSEndpoint could not be instantiated: " + lEx.getMessage());
   System.exit(0);
}

Instantiating the Authenticator classes

For security reasons there are no user credential passed anymore within the tokens. All request tokens sent to you application destination will now contain OAuth “access tokens” as a representation of a user. These access tokens are trustworthy and your application can easily obtain the real user name for its authorization purposes from this access token.

Three authenticators are available:

final JWSAutoSelectAuthenticator lAuthenticator = new JWSAutoSelectAuthenticator();
final JWSOAuthAuthenticator lOAuthAuthenticator = new JWSOAuthAuthenticator();
final JWSLDAPAuthenticator lLDAPAuthenticator = new JWSLDAPAuthenticator();

You application is supposed to use the JWSAutoSelectAuthenticator instance, this automatically selects the appropriate authentication method according to the token content (access token or credentials to keep upward compatible). The JWSOAuthAuthenticator instance is required to support SSO via OAuth and the JWSLDAPAuthenticator instance is required to support authentication against a certain Active Directory via LDAP.

Configuring the SSO/OAuth authenticator:

To configure the OAuth authenticator the following sample code can be used (lConfig here is a taken from the Apache Commons Config library for convenience):

// set up OAuth Authenticator
boolean lUseOAuth = lConfig.getBoolean("UseOAuth", false);

String lOAuthHost = lConfig.getString("OAuthHost");
String lOAuthAppId = lConfig.getString("OAuthAppId");
String lOAuthAppSecret = lConfig.getString("OAuthAppSecret");
String lOAuthUsername = lConfig.getString("OAuthUsername");
String lOAuthPassword = lConfig.getString("OAuthPassword");
long lOAuthTimeout = lConfig.getLong("OAuthTimeout", 5000);

lUseOAuth = lUseOAuth
  && null != lOAuthHost
  && null != lOAuthAppId
  && null != lOAuthAppSecret
  && null != lOAuthUsername
  && null != lOAuthPassword;

if (lUseOAuth) {
  lOAuthAuthenticator.init(
      lOAuthHost,
      lOAuthAppId,
      lOAuthAppSecret,
      lOAuthUsername,
      lOAuthPassword,
      lOAuthTimeout
   );
   lAuthenticator.addAuthenticator(lOAuthAuthenticator);
}
Configuring the LDAP authenticator:

To support the AD/LDAP fallback in case old tokens need to be authenticated or the SSO/OAuth system fails your application can use an LDAP authenticator as fallback. This needs to be configured as follows:

// set up LDAP Authenticator
boolean lUseLDAP = lConfig.getBoolean("UseLDAP", false);

String lLDAPURL = lConfig.getString("LDAPURL");
String lBaseDNGroups = lConfig.getString("BaseDNGroups");
String lBaseDNUsers = lConfig.getString("BaseDNUsers");

if (lUseLDAP) {
   lLDAPAuthenticator.init(
      lLDAPURL,
      lBaseDNGroups,
      lBaseDNUsers
   );
   lAuthenticator.addAuthenticator(lLDAPAuthenticator);
}

As you can see in the both above code snippets the two authenticators are added to the JWSAutoSelectAuthenticator instance. So this instance can make use of both authentication methods, your application does not need to take care of this or to distinguish between the various tokens, all this effort is taken over by the new JMSEndPoint library.

Using MemoryAuthenticator:

Using the Memory authenticator makes sense for testing purposes and also for environments where we don't have any OAuth2 or LDAP servers.
So the usage is pretty simple:

  final JWSMemoryAuthenticator lMemoryAuthenticator = new JWSMemoryAuthenticator();
  // setup memory authenticator
  boolean lUseMemory = true;
  if (lUseMemory) {
      // statically adding user credentials (password require to be MD5 value)
      lMemoryAuthenticator.addCredentials("root", "63a9f0ea7bb98050796b649e85481845"); //root:root
      // registering the authenticator
      lAuthenticatorManager.addAuthenticator(lMemoryAuthenticator);
  }  

Using the AutoSelect Authenticator:

Here you find an example for a request authentication using the AutoSelectAuthenticator:

// test for the auto authentication interface
lJWSEndPoint.addRequestListener(
   "org.jwebsocket.jms.demo", "testAuth", new JWSMessageListener(lJWSEndPoint) {
@Override
public void processToken(String aSourceId, Token aToken) {

   mLog.debug("Testing auto authenticator...");
   Map<String, Object> lArgs = new FastMap<String, Object>();

   String lUsername;
   int lCode = 0;
   String lMessage = "Ok";
   try {
      checkADUsername(aToken);
      lUsername = lAuthenticator.authToken(aToken);
      if (null == lUsername) {
         lCode = -1;
         lMessage = "User could not be authenticated!";
      } else {
         lArgs.put("username", lUsername);
      }
   } catch (JMSEndpointException lEx) {
      lCode = -1;
      lMessage = lEx.getClass().getSimpleName()
      + " on auto authentication: " + lEx.getMessage();
   }

   lJWSEndPoint.respondPayload(
      aToken,
      lCode, // return code
      lMessage, // return message
      lArgs, // additional result fields
      null); // payload
   }
});
Authentication against jWebSocket:

jWebSocket sends a “Welcome” token when a new endpoint connects to the Message Queue. This “Welcome” must be used to start authenticate against jWebSocket to be able to access file systems or databases through the various jWebSocket services.

lJWSEndPoint.addRequestListener(
"org.jwebsocket.jms.gateway", "welcome", new JWSMessageListener(lJWSEndPoint) {
   @Override
   public void processToken(String aSourceId, Token aToken) {
      mLog.info("Received 'welcome', authenticating against jWebSocket...");
      Token lToken = TokenFactory.createToken("org.jwebsocket.plugins.system", "login");
      lToken.setString("username", lJWSUsername);
      lToken.setString("password", lJWSPassword);
      sendToken(aSourceId, lToken);
   }
}
);

Your application needs to listen to the response from jWebSocket to verify that the authentication was successful. For this you can use the following response listener:

// on response of the login...
lJWSEndPoint.addResponseListener(
 "org.jwebsocket.plugins.system", "login", new JWSMessageListener(lJWSEndPoint) {
   @Override
   public void processToken(String aSourceId, Token aToken) {
      int lCode = aToken.getInteger("code", -1);
      if (0 == lCode) {
         if (mLog.isInfoEnabled()) {
             mLog.info("Authentication against jWebSocket successful.");
         }
      } else {
         mLog.error("Authentication against jWebSocket failed!");
      }
   }
});
Creating progress events:

The following code snippet shows how long lasting process easily can send progress events to the requestor to notify him about the current status, before sending the final response:

// on response of the login...
lJWSEndPoint.addRequestListener(
 "org.jwebsocket.jms.demo", "testProgress", new JWSMessageListener(lJWSEndPoint) {
   @Override
   @SuppressWarnings("SleepWhileInLoop")
   public void processToken(String aSourceId, Token aToken) {
      int lMax = 10;
      for (int lIdx = 0; lIdx < lMax; lIdx++) {
         mLog.debug("Progress iteration " + lIdx + "...");
         try {
            Thread.sleep(333);
         } catch (InterruptedException lEx) {
         }
         lJWSEndPoint.sendProgress(
           aToken,
           ((lIdx + 1.0) / lMax) * 100, 0,
           "Iteration #" + lIdx, null);
      }
      lJWSEndPoint.respondPayload(
      aToken,
      0, // return code
      "Ok", // return message
      null,
      aToken.getString("payload"));
   }
});
The jWebSocket ActiveMQ Whitelist and Blacklist Authorization Plug-in:

To authorize certain hosts to use specific endpoint ids only jWebSocket comes with an authorization plug-in, which provides a white list and a black list to control which physical hosts/ips are allowed to connect to a certain message queue/topic by using a which endpoint id. The major purpose is to prevent that an arbitrary host identifies with an endpoint id which is reserved for a certain service.

In case the authorization plug-in detects a connection request using a certain endpoint id from a host which is either:

  • not matching the white list
  • or matching the black list

it gets rejected in the same way like trying to connect with the same endpoint id to the same queue/topic twice. For the application there already is (should be) and exception handler which processes a rejected connection, thus no effect in the application side is expected by this additional security plug-in.

jWebSocketActiveMQPlugIn.jar

The jWebSocket ActiveMQ Whitelist and Blacklist Authorization Plug-in is provided as Java .jar file jWebSocketActiveMQPlugIn-1.0.jar and needs to be located in the ${ACTIVEMQ_HOME}/lib folder. You will see that it is loaded when you start the activemq.bat in the console like this:

ACTIVEMQ_HOME: C:\Program Files (x86)\Apache Software Foundation\ActiveMQ-5.8.0
ACTIVEMQ_BASE: C:\Program Files (x86)\Apache Software Foundation\ActiveMQ-5.8.0
ACTIVEMQ_CONF: C:\Program Files (x86)\Apache Software Foundation\ActiveMQ-5.8.0\conf
ACTIVEMQ_DATA: C:\Program Files (x86)\Apache Software Foundation\ActiveMQ-5.8.0\data
Loading message broker from: xbean:activemq.xml
INFO | Refreshing org.apache.activemq.xbean.XBeanBrokerFactory$1@2bb34e41: startup date [Wed Jul 09 11:22:20 CEST 2014]; root of context hierarchy
INFO | Instantiating jWebSocket AMQBasicSecurityPlugIn...
INFO | PListStore:[C:\Program Files (x86)\Apache Software Foundation\ActiveMQ-5.8.0\data\localhost\tmp_storage] started
INFO | Using Persistence Adapter: KahaDBPersistenceAdapter[C:\Program Files (x86)\Apache Software Foundation\ActiveMQ-5.8.0\data\kahadb]
INFO | KahaDB is version 4
INFO | Recovering from the journal ...
INFO | Recovery replayed 47 operations from the journal in 0.052 seconds.
INFO | Installing jWebSocket AMQBasicSecurityPlugIn...
INFO | Apache ActiveMQ 5.8.0 (localhost, ID:ASCHULZE-DT-61384-1404897742121-0:1) is starting
INFO | Listening for connections at: tcp://ASCHULZE-DT:61616?maximumConnections=1000&wireformat.maxFrameSize=...
INFO | Connector openwire Started
INFO | Listening for connections at: stomp://127.0.0.1:61613?maximumConnections=1000&wireformat.maxFrameSize=104857600
INFO | Connector stomp Started
INFO | Listening for connections at: amqp://ASCHULZE-DT:5672?maximumConnections=1000&wireformat.maxFrameSize=104857600
INFO | Connector amqp Started
INFO | jWebSocket AMQBasicSecurityPlugIn checking host tcp://127.0.0.1:61383...
INFO | jWebSocket AMQBasicSecurityPlugIn accepted host 127.0.0.1 (IP: 127.0.0.1).
INFO | Connector websocket Started
INFO | Apache ActiveMQ 5.8.0 (localhost, ID:ASCHULZE-DT-61384-1404897742121-0:1) started
INFO | For help or more information please see: http://activemq.apache.org
INFO | Web console type: embedded
INFO | ActiveMQ WebConsole initialized.
INFO | Initializing Spring FrameworkServlet 'dispatcher'
INFO | jolokia-agent: No access restrictor found at classpath:/jolokia-access.xml, access to all MBeans is allowed
whitelist.properties

The white list contains all hosts that are allowed to connect with a certain endpoint id.
The white list is managed as key/value pairs in the file whitelist.properties in ${ACTIVEMQ_HOME}/home. In general the white list specifies a list of endpoint ids and relates them to a list of host names and/or IPs. The format of each line is:

<endpointid>=<hostname|IP>[,<hostname|IP>]...

blacklist.properties

The black list contains all hosts that are disallowed to connect with a certain endpoint id.
The black list is managed as key/value pairs in the file blacklist.properties in ${ACTIVEMQ_HOME}/home. In general the black list specifies a list of endpoint ids and relates them to a list of host names and/or IPs. The format of each line is:

<endpointid>=<hostname|IP>[,<hostname|IP>]...

Examples:

A "normal" entry in the white list or black list:

aschulze-dt.jwebsocket.org=192.168.2.92,ASCHULZE-DT

You also can use wildcards (e.g. to allow/disallow host names with and with out full domain name):

aschulze-dt*=192.168.2.92,ASCHULZE-DT*

activemq.xml

The following configuration needs to be inserted into the activemq.xml configuration file in ${ACTIVEMQ_HOME}/home.

<broker>
:
   <plugins>
      <bean xmlns="http://www.springframework.org/schema/beans"
      id="jwsBasicSecurityPlugIn"
      class="org.jwebsocket.amq.AMQBasicSecurityPlugIn">
         <constructor-arg value="whitelist.properties"/>
         <constructor-arg value="blacklist.properties"/>
      </bean>
   </plugins>
:
</broker>

Copyright © 2013 Innotrade GmbH. All rights reserved.