Configuring a MongoDB Login Module

Creating a Login Module with JBoss AS 7 or WildFly can be done by extending some of the available PicketBox Login modules. See this tutorial for a quick introduction to Custom Login modules: Creating a Custom JBoss Login Module. Here we will learn how to create a custom Login Module which used MongoDB for performing JAAS authentication.

First of all, start your MongoDB instance:

mongod

 Now connect with your Mongo shell:

mongo

 We will create a single document in a collection named “jbosslogin”. This document contains the username, the password and the group to which the user belongs:

db.jbosslogin.insert({name:'user1',password:'Password1',group:'Manager'})

 Expected outcome from the shell:

mongodb java jboss

Ok, this will be enough for a simple test. Now download MongoDB JDBC driver which is available here. Once downloaded, start your favourite IDE and create the following Login Module class:

package com.mastertheboss;

import java.net.UnknownHostException;
import java.security.acl.Group;

import java.util.Map;

import javax.security.auth.Subject;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.login.LoginException;

import org.jboss.security.SimpleGroup;
import org.jboss.security.SimplePrincipal;
import org.jboss.security.auth.spi.UsernamePasswordLoginModule;

import com.mongodb.*;
/**
 * 
 * @author Francesco Marchioni
 * 
 */
public class MongoLoginModule extends UsernamePasswordLoginModule {
	String userGroup;

	public void initialize(Subject subject, CallbackHandler callbackHandler,
			Map sharedState, Map options) {
		super.initialize(subject, callbackHandler, sharedState, options);
	}

	/**
	 * (required) The UsernamePasswordLoginModule modules compares the result of
	 * this method with the actual password.
	 */
	@Override
	protected String getUsersPassword() throws LoginException {
		System.out.format("MyLoginModule: authenticating user '%s'\n",
				getUsername());
		String password = super.getUsername();
		password = password.toUpperCase();
		return password;
	}

	/**
	 * (optional) Override if you want to change how the password are compared
	 * or if you need to perform some conversion on them.
	 */
	@Override
	protected boolean validatePassword(String inputPassword,
			String expectedPassword) {

		String encryptedInputPassword = (inputPassword == null) ? null
				: inputPassword.toUpperCase();
		System.out.format("Validating that (encrypted) input psw '%s' equals to (encrypted) '%s'\n",
						encryptedInputPassword, expectedPassword);
		MongoClient mongoClient = null;
		try {
			mongoClient = new MongoClient("localhost");
		} catch (UnknownHostException e) {
			e.printStackTrace();
		}
		DB db = mongoClient.getDB("test");
		DBCollection coll = db.getCollection("jbosslogin");
		BasicDBObject query = new BasicDBObject("name", getUsername());

		DBCursor cursor = coll.find(query);

		try {
			if (cursor.hasNext()) {
                            // User found in DB
   			    BasicDBObject obj = (BasicDBObject) cursor.next();

				String password = (String) obj.get("password");
				userGroup = (String) obj.get("group");
				if (inputPassword.equals(password)) {
					System.out.println("Password matching");
					return true;
				}
			} else {
				System.out.println("User not found!");
				return false;
			}
		} finally {
			cursor.close();
		}

		return false;
	}

	@Override
	protected Group[] getRoleSets() throws LoginException {
		SimpleGroup group = new SimpleGroup("Roles");
		try {
            // userGroup picked up by MongoDB Cursor earlier
			group.addMember(new SimplePrincipal(userGroup));
		} catch (Exception e) {
			throw new LoginException("Failed to create group member for "
					+ group);
		}
		return new Group[] { group };
	}

}

As you can see, the most interesting part is the validatePassword method which checks on the DB if the password entered by the user matches with the password found in the DB. After the password check, an Array of Group is returned, containing the group found in the BasicDBObject cursor.

The following libraries will be needed in order to compile this project:

mongodb java jboss wildfly

Save this class and pack it in a JAR file, let’s say mongo-loginmodule.jar. We will now provide it to the application server as a module.

Creating the module on the application server

We will create the following structure under the modules folder, which includes the mongo-loginmodule.jar, the Mongo JDBC driver and a module.xml file:

C:\Wildfly-8.2.0.Final\modules
”œ”€”€”€mongologin
”‚   ”””€”€”€main
”‚           LoginModule.jar
”‚           module.xml
”‚           mongo-java-driver-2.13.0-rc2.jar

Within the module.xml we will declare the list of resources contained in the module and the module name as well:

<module xmlns="urn:jboss:module:1.1" name="mongologin">
  <resources>
    <resource-root path="LoginModule.jar"/>
    <resource-root path="mongo-java-driver-2.13.0-rc2.jar" />
  </resources>

  <dependencies>
    <module name="org.picketbox"/>
    <module name="javax.api"/>
  </dependencies>
</module>

Declaring the Security Domain in the application server configuration

Last step will be including a Security domain in the application server configuration which uses the module we have just installed:

<subsystem xmlns="urn:jboss:domain:security:1.2">
	<security-domains>
		<security-domain name="mongo-auth" cache-type="default">
			<authentication>
				<login-module code="com.mastertheboss.MongoLoginModule" flag="required" module="mongologin"/>
			</authentication>
		</security-domain>
. . . .
	</security-domains>
</subsystem>

That’s all. In order to use the login module in your application, include the security domain named “mongo-auth”. For example, in a Web application, include in your jboss-web.xml configuration file the following:

<jboss-web>
    <security-domain>mongo-auth</security-domain>
</jboss-web>

Further improvements

You can further improve the MongoDB Login module by specifying the Database and collection name as module options:

<subsystem xmlns="urn:jboss:domain:security:1.2">
	<security-domains>
		<security-domain name="mongo-auth" cache-type="default">
			<authentication>
				<login-module code="com.mastertheboss.MongoLoginModule" flag="required" module="mongologin">
                                     <module-option name="database" value="test"/>
                                     <module-option name="collection" value="jbosslogin"/>
                                </login-module>
			</authentication>
		</security-domain>
. . . .
	</security-domains>
</subsystem>

You will need to include two additional properties in your LoginModule named database and collection, which will replace the hardcoded values we have used so far:

public class MongoLoginModule extends UsernamePasswordLoginModule {
	String userGroup;

        String database;
        String collection;

. . .
	@Override
	protected boolean validatePassword(String inputPassword,
			String expectedPassword) {

		. . .
		DB db = mongoClient.getDB(database);
		DBCollection coll = db.getCollection(collection);
		BasicDBObject query = new BasicDBObject("name", getUsername());

		DBCursor cursor = coll.find(query);
        . . .
        }

}