Skip to main content

At Tomitribe, much of our client work focuses on integration. For example, a customer might come to us and with an existing LDAP server that they want to use as a security realm or, in this case, a requirement to connect to a MongoDB database that contains users, credentials, and role definitions. The great thing about Tomcat and TomEE is that it’s very easy to write custom components that can use any external resource to do just about anything. While there are likely several valid ways to use a MongoDB database as an authentication provider for Tomcat, in this blog post you’ll see how to drop in a simple Realm that points to MongoDB for authentication.

Integration at the container level is something we specialize in, and it is also the best choice for organizations that are trying to think at a higher level than the application. When attempting to solve problems like security at the application level in a large organization you end up having to coordinate a hundred different groups with a hundred different release schedules to implement the same standard.  Alternatively, when you implement something like security at the container level you provide a universal infrastructure to all applications across the organization without all the cat herding. Logging, database management, security, service management – these all make much more sense to manage at the container-level.

MongoDB as a Tomcat Realm

MongoDB_Logo MongoDB is a popular NoSQL database with a special emphasis on documents. It’s a mature, open-source product with a large community, and the project has always had a focus on interoperability and ease-of-use. The installation process for MongoDB takes no more than a few minutes, and it’s a product that’s designed to just work out-of-the-box. If you need a quick way to store and retrieve documents and if you want something that can understand JSON – MongoDB is a good choice.

If you understand the use-cases that are appropriate for a document database and if you optimize MongoDB in a production environment – MongoDB can save you on two fronts: for certain problems a document access pattern is going to be much more efficient and MongoDB lends itself to more flexible schemas that need to change over time. I’ve depended on MongoDB in production, and I can attest to both its utility and stability over time. It has allowed me to shift load that was inappropriate for a RDBMS to something more amenable to load that is heavy on either read or write operations. Authentication information at a large organization is often characterized by very infrequent writes and very frequent reads and the nature of data lends itself to a document format where users and roles are combined into a single document.

A Simple MongoRealm for Tomcat

I threw together a very simple implementation of a Realm that uses MongoDB as an authentication source for Tomcat.  I wrote this because I couldn’t find an implementation that deferred to the MongoClient and supported the syntax of the the MongoClientURI.  I needed a Realm that could connect to multiple members of a replicaSet and which could understand preferences for reading from either a primary or a secondary.   I also needed something simple and configurable enough to allow the user to point to whichever collection and field name contained the necessary credential information.  In other words, I need the MongoRealm equivalent of the JDBCRealm, and none of the existing solutions fit these requirements.

The MongoRealm

Here it is. The project is available here on GitHub – https://github.com/tobrien/mongo-realm

To use it, you’ll have to build it.  Using Maven 3, just run “mvn clean install”, and then you’ll have a target/mongo-realm-1.0-SNAPSHOT.jar ready to copy to your Tomcat’s lib/ directory.  It’s a simple project that consists of a single class, and I’ve purposefully steered clear of making it complex.  I wanted to create a clear and concise example that could be understood and customized very quickly because I’m hoping that others with a similar requirement will dive in and start making some pull requests.

To make use of this Realm…

  1. Download MongoDB, fire it up locally on the default port: 27017
  2. Build the mongo-realm and copy mongo-realm-1.0-SNAPSHOT.jar to your $TOMCAT_HOME/lib directory
  3. Copy the MongoDB Java Driver to your $TOMCAT_HOME/lib directory.  (Note that if you used Maven to build this Realm, this file should be in ~/.m2/repository/org/mongodb/mongo-java-driver/2.11.3/mongo-java-driver-2.11.3.jar)
  4. Create a new Mongo database named “security_realm” (if you don’t already have a database with users and credentials).  In MongoDB, it’s easy to do this, just run “use security_realm” in the Mongo client.  If Mongo doesn’t have a database or collection that you reference, it just creates it.
  5. Create a user collection with some test information.  To create a new user “tomee” with the password “tomee” that has been SHA-256’d and with one role “tomee”.  Run
    db.user.insert( { username: "tomee", 
             credentials: "4c6a855adfb64104b6ba85cb3c8823b79302f2f25f33d5a27d51b800393e5cc6",
             roles: [ { name: "tomee" } ]);
  6. Configure your new Realm using the defaults (connects to localhost:27017 and uses the default database, collection, and field names):
    <Realm className="com.tomitribe.security.MongoRealm" 
                       digest="SHA-256"/>
  7. Fire up TomEE and then load the TomEE console here: http://localhost:8080/tomee/ – you should be able to login with the user “tomee” and “tomee”. Enjoy.

If everything works properly, you should see something like this in catalina.out:

Oct 21, 2013 9:47:07 AM org.apache.openejb.client.EventLogger log
INFO: RemoteInitialContextCreated{providerUri=http://127.0.0.1:8080/tomee/ejb}
Oct 21, 2013 9:47:07 AM com.tomitribe.security.MongoRealm authenticate
INFO: Authentication Success for tomee

Customizing MongoRealm Configuration

If you are using Mongo in production then there’s a good chance you are not running MongoDB on localhost.  In fact, you are probably running a three member replicaSet with a primary that can move between the three members of the replicaSet on an as-needed basis.  (Note: If you are not, you really should be running at least a three member replicaSet.)   In this scenario you are going to want to change the mongoClientURI to contain more than one server and you may want to configure preferences for which node you read for credential information.

If you are trying to use an existing repository of usernames, passwords, and roles in MongoDB, there’s also a good chance that you want to know how to customize the names of the database, the collection, and the field names you are using to retrieve user data from Mongo.  All of these options can be configured by following the configuration instructions on the MongoRealm project page: https://github.com/tobrien/mongo-realm – Here’s the Realm configuration with all of the defaults listed explicitly:

<Realm className="com.tomitribe.security.MongoRealm"
       digest="SHA-256"
       mongoClientURI="mongodb://localhost/"
       database="security_realm"
       userCollection="user"
       usernameField="username"
       credentialsField="credentials"
       rolesField="roles"
       roleNameField="name"/>

Next steps…

This realm leaves much to be desired, but it’s a starting point.  Here are some of the things that could be improved (feel free to fork and make a pull request if you scratch one of these itches):

  • Error handling and logging.  Right now, this Realm works well if everything works well.  It needs to be tested exhaustively, and the error and warning it produces should be improved.
  • Caching and Performance. If you look at the MongoRealm, you’ll notice that there’s very little effort made to reuse a document retrieved through the authentication request.  While I could have used a ThreadLocal variable, I decided to err on the side of simplicity.  Eventually it might make sense to cache user authentication information in a local cache to reduce the chattiness between TomEE and MongoDB.
  • Indexes. This documentation may be sufficient for a simple, small database, but if you want this Realm to perform for a large number of rows without having to resort to a full collection scan you’ll need to define an index.
  • Flexible Role Mapping. If you’ve ever worked with LDAP, you’ll understand that there are a host of different ways to model users and roles.  This Realm implementation assumes that roles are going to be stored in a user object, but what if someone takes a different approach and creates a separate Role document that contains a list of member users.  This Realm should be able to support more complex user-role mapping scenarios.
  • You’ll notice that this simplistic approach is also very document oriented. There is a user document which contains one or more objects in a roles array. This approach is very different from a relational approach to storing data, and this has ramifications on the implementation of this Realm. The MongoRealm as opposed to the JDBCRealm retrieves a single document and iterates through that document to create an ArrayList of role names.

 

Leave a Reply