Custom JDBC (MySQL) Realm Authentication with Livecycle Data Services (or BlazeDS)

LCDS ships with the default Tomcat roles based security ready to go. You simply add some users to the tomcat-users.xml and there you go. This is probably fine for some, but for my needs I want to be able to authenticate against a MySQL database. I addition, I want to use strong encryption to protect my users personal information, as well as potentially sensitive business data. In this article I am going to explain how to configure Tomcat and Livecycle Data Services to do just that. It is actually fairly painless, once you get all the pieces put together. Hopefully this will be an area of the LCDS documentation that will get some attention in the future.

There are many robust frameworks available to Java that provide authentication functionality. In addition to that, they provide a level of complexity that I want to avoid. My primary purpose is creating enterprise Flex/AIR applications with a strong service layer provided by LCDS. I simply don't want to learn the intrict workings of Spring, JBoss Websphere, or any of the other fine solutions that might solve this particular problem. What I want is a simple solution that provides the functionality I need without a host of extra features I won't ever use. My finite capacity for new information needs all the filtering it can get.

The following assumes you are using the turnkey Tomcat LCDS installation.

Setting up the MySQL Database

Any JDBC connectable database solution should work, so long as the proper tables are in place. MySQL is used in this example simply for convenience.

Tomcat's realms (discussed below) require some specific tables in your database. It needs a User table that identifies the users and their passwords. In addition, there needs to be a user_roles table that corresponds to each user and their role(s) in the application.

CREATE TABLE users (
  username         varchar(15) NOT NULL PRIMARY KEY,
  password         varchar(15) NOT NULL
);
 
CREATE TABLE user_roles (
  username          varchar(15) NOT NULL,
  role              varchar(15) NOT NULL,
  PRIMARY KEY (username, role)

);

The above would be the be sufficient to provide JDBCRealm the information it needs for authentication. While I'm sure it would be possible to extend the JDBC realm further than I do in this walk-through

Configuring the Realm

Tomcat implements realms for security. At its simplest form we have the RealmBase class. This provides the functionality to parse simple XML files for users, passwords, and roles. In addition, there is a realm class JDBCRealm. This realm is a lot closer to what we actually need, and would probably be sufficient in many cases. While the JDBCRealm provides for the digesting of passwords into encrypted hashes for storage in the databse, I want to use a more robust encryption solution. Jasypt (Java Simplified Encryption) provides this, as well as transparent integration with Hibernate. This lets me put user's passwords in the database with salted hashes, and also provides the flexibility to encrypt virtually any other field in my data model.

To facilitate this addition, we need to extend the JDBCRealm, overriding the authenticate method to remove the default digest calls and replace them with the Jasypt StrongPasswordEncryptor.

package realm; 
 
import org.apache.catalina.realm.*;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
 
import java.security.*;
import java.sql.Connection;
import java.util.ArrayList;
 
import org.jasypt.util.password.StrongPasswordEncryptor; 
 
public class MyRealm extends JDBCRealm {      
 
      private static Log containerLog = LogFactory.getLog(SAIRealm.class); 
 
      StrongPasswordEncryptor passwordEncryptor = new StrongPasswordEncryptor();      
 
      @Override
      public synchronized Principal authenticate(Connection dbConnection,
                                                 String username,
                                                 String credentials)
      {
            if (username == null) {
                  return (null);
            }            
 
            String dbCredentials = getPassword(username);               
 
            boolean validated = passwordEncryptor.checkPassword(credentials, dbCredentials);
            if (validated) {
                  if (containerLog.isTraceEnabled())
                       containerLog.trace(sm.getString("jdbcRealm.authenticateSuccess",
                       username));
            } else {
                  if (containerLog.isTraceEnabled())
                       containerLog.trace(sm.getString("jdbcRealm.authenticateFailure",
                       username));
                  return (null);
            }            
 
            ArrayList<String> roles = getRoles(username);
 
            return (new GenericPrincipal(this, username, credentials, roles));
      }
}

This class needs to be compiled and placed into a Jar (FlexBuilder/Exlipse makes this easy through the file>export menu). The compiled class can now be added to the {lcds}/tomcat/lib folder so that it will be available to the server.

Now that we've added our custom realm to the server, we need to add it to the configuration files so that it is recognized.

In {lcds}\tomcat\conf\server.xml find the existing realm, and remove it (this might be something you can configure, multiple realms, but I didn't figure it out and don't need the stock XML database so I just killed it):

<Engine name="Catalina" defaultHost="localhost">
      ...
      <Realm className="realm.MyRealm"
           debug="99"
           driverName="com.mysql.jdbc.Driver"
           connectionURL="jdbc:mysql://localhost/mydatabase?user=dbUsername&amp;password=dbPassword"
           userTable="user" userNameCol="username" userCredCol="password"
           userRoleTable="user_roles" roleNameCol="role"/>
      ...
</Engine>

In addition, your application's context file needs to supply information about the realm:

{lcds}/tomcat/conf/Catalina/localhost/{application_context}.xml

<Realm className="realm.MyRealm"
           debug="99"
           driverName="com.mysql.jdbc.Driver"
           connectionURL="jdbc:mysql://localhost/mydatabase?user=dbUsername&amp;password=dbPassword"
           userTable="user" userNameCol="username" userCredCol="password"
           userRoleTable="user_roles" roleNameCol="role"/>
     <Valve className="flex.messaging.security.TomcatValve"/>

I don't know if the additional database information is actually neccessary here, but it doesn't hurt anything, so there it is. This is also the place to define the Tomcat Valce so that Flex/LCDS can communicate with our custom realm. Note that it is important to FULLY qualify your classname for both the custom realm and the database connector classes.

Configuring the Services

<security>
        <login-command class="flex.messaging.security.TomcatLoginCommand" server="Tomcat">
<per-client-authentication>false</per-client-authentication>
        </login-command>
        <security-constraint id="jdbc-auth">
            <auth-method>Custom</auth-method>
            <roles>
                  <role>super</role>
                  <role>admin</role>
                  <role>user</role>
                  <role>client</role>
            </roles>
        </security-constraint>
      </security>

This is the basic configuration. Now you simply need to add the security tag to your destinations to enable.

<destination id="SecurePojo1">
        ...
        <security>
            <security-constraint ref="jdbc-auth"/>
        </security>
</destination>

You can also apply the constraint globally.

<service>
     ...
    <default-security-constraint ref="jdbc-auth"/>
</service>

or to individual methods

<destination id="sampleIncludeMethods">
<properties>
        <source>my.company.SampleService</source>
        <include-methods>
            <method name="fooMethod"/>
            <method name="barMethod" security-constraint="jdbc-auth"/>
        </include-methods>
    </properties>
    <security>
        <security-constraint ref="jdbc-auth"/>
    </security>
</destination>

The Client

All of the complex bits are now taken care of. When you try to access a tagged destination you are flatly denied. To authenticate from Flex, your ChannelSet will need to login to the service. I use AIR, so I define my ChannelSets in the application, but Flex apps will automagically configure this for you and they are 'hidden'. To get at your ChannelSet in flex you need to use the ServerConfig class:

import mx.messaging.config.ServerConfig;
...
myChannelSet=ServerConfig.getChannelSet(remoteObject.destination);
myChannelSet.login("username","password");

If you have place in the main mxml file of your application and stare at the LCDS/Tomcat console, you should see a successful login. If not, I apologize for failing you up to this point ;(

One thing that the realm does NOT provide is a way to add new users or manipulate the database. I plan on getting into this in future articles, so stay tuned.

These files need to be added to the {lcds}/tomcat/lib folder:
icu4j-4_0.jar
jasypt-1.5.jar
commons-codec-1.1.jar
commons-lang-2.1.jar
mysql-connector-java-5.1.6-bin.jar
myrealm.jar

3 Responses to “Custom JDBC (MySQL) Realm Authentication with Livecycle Data Services (or BlazeDS)”


  1. 1 Alexandro

    Great article.
    How would I go about doing this with the integrated CF8 install of LCDS that runs on Jrun instead of Tomcat.

    regards,

    Alexandro

  2. 2 Joel

    Alexandro,

    I wouldn’t know where to begin. I wish I could be of more assistance,

    Best,

    Joel

  3. 3 Alexandro

    I got it figured our. I used the multiserver install which comes with the JMC for Jrun.
    That made it easy adding datasource to jrun generating the needed code for jrun.
    The I just used the jrunlogincommand in lcds and presto!!

Leave a Reply