Tuesday, February 24, 2009

Equinox : Authenticate with Database Login using Equinox Security

Most of the desktop applications require a authentication step when the application is started. Eclipse does not yet provide a login mechanism to authenticate the user. But the equinox security has it on the proposal to support login dialog based authentication. Till then, what would be the best way to do the authentication with existing equinox security features. Lets have a look at RCP Mail Template with a login dialog during start up.

Equinox implements the standard Java Authentication and Authorization Service ( JAAS ) to authenticate and authorize. We will look at some realtime examples of how to do the authentication.

Equinox supports extension login modules which can be configured via the loginmodule extension. The extension can be configured with nt login module, LDAP login modules from the sun security modules or can be configured with custom written LoginModules like RDBMSLoginModule, SecureStorage Logins and so on.

The example from the org.eclipse.equinox.security.sample from the equinox cvs (org.eclipse.equinox/incubator/security/bundles/org.eclipse.equinox.security.sample ) /cvsroot/rt explains adding LDAP, WIN32 and other login modules to the application. Here in this example we will write a custom LoginModule which authenticates against a database connection.

RDBMSLoginModule - The login modules has to implement LoginModule interface from javax security. The LoginModule contains initialize, login, logout, commit and abort methods. The initialize method gives the CallbackHanlder and other options.

The Login Method which does the authentication has to be implemented against a database for this example.

public boolean login() throws LoginException
{

if (callbackHandler == null)
throw new LoginException("Error: no CallbackHandler available "
+ "to garner authentication information from the user");

try
{
// Setup default callback handlers.
Callback[] callbacks = new Callback[] { new NameCallback("Username: "),
new PasswordCallback("Password: ", false), new NameCallback("Database: ") };

callbackHandler.handle(callbacks);

String username = ((NameCallback) callbacks[0]).getName();
String password = new String(((PasswordCallback) callbacks[1]).getPassword());
String dbname = ((NameCallback) callbacks[2]).getName();

((PasswordCallback) callbacks[1]).clearPassword();

success = rdbmsValidate(username, password,dbname); // This method should try to connect
//to the database with the given username,password, url and return true on success.

callbacks[0] = null;
callbacks[1] = null;

if (!success)
throw new LoginException("Authentication failed: Password does not match");

return (true);
}
catch (LoginException ex)
{
throw ex;
}
catch (Exception ex)
{
success = false;
throw new LoginException(ex.getMessage());
}
}
The LoginModule is declared with org.eclipse.equinox.security.loginModule extension point.


<extension
id="com.sample.login.RdbmsLoginModule"
point="org.eclipse.equinox.security.loginModule">
<loginModule
class="com.sample.login.RdbmsLoginModule">
</loginModule>
</extension>

The login method calls the callback handler which is configured in the plugin.xml via the extension org.eclipse.equinox.security.callbackHandler

<extension
id="com.sample.login.LoginDialogCallbackHandler"
point="org.eclipse.equinox.security.callbackHandler">
<callbackHandler
class="com.sample.login.LoginDialogCallbackHandler">
</callbackhandler>
</extension>


The call back handler is mapped to a configName in this case say RDBMS to a Dialog which takes username, password and the db url.

The CallBackHandlerMapping is done as follows

<extension
point="org.eclipse.equinox.security.callbackHandlerMapping">
<callbackHandlerMapping
callbackHandlerId="com.sample.login.LoginDialogCallbackHandler"
configName="RDBMS">
</callbackHandlerMapping>
</extension>

Once the rdbmsValidate method successfully connects to the database with the given values , a javax.security.auth.Subject can be constructed with the Credentials and Principals as an authenticated user.

private boolean rdbmsValidate(String user, String pass) throws Exception
{

Connection con;
boolean passwordMatch = true;

try
{
Class.forName(driverClass);
}
catch (java.lang.ClassNotFoundException e)
{
System.err.print("ClassNotFoundException: ");
System.err.println(e.getMessage());
throw new LoginException("Database driver class not found: " + driverClass);
}

try
{
if (debug)
System.out.println("\t\t[RdbmsLoginModule] Trying to connect...");

con = DriverManager.getConnection(url, user, pass);
if(con == null )
passwordMatch = false;
else
//Construct a subject.
} catch(Exception ex) {
}
return (passwordMatch)'
}

Now that all the extension points are made for the authentication. We need to call the LoginModule at the appropriate place on the application. Usually the Eclipse Application start method is a good place to keep the Authentication process.

Create the LoginContext from the RDBMS callback handler defined in the plugin.xml.

public Object start(final IApplicationContext context) throws Exception
{
String configName = Activator.getConfigurationName();
URL configUrl = Activator.getBundleContext().getBundle().getEntry("jaas_config.txt");
ILoginContext secureContext = LoginContextFactory.createContext(configName, configUrl);
secureContext.registerListener(new ProgressMonitorListener());
Integer result = null;
final Display display = PlatformUI.createDisplay();
try
{
result = (Integer) Subject.doAs(secureContext.getSubject(), getRunAction(display));
}
finally
{
display.dispose();
secureContext.logout();
}
// TBD handle javax.security.auth.login.LoginException

if (result != null && PlatformUI.RETURN_RESTART == result.intValue())
return EXIT_RESTART;
return EXIT_OK;
}


Add the following methods to the Activator class of the plugin.


private static final String CONFIG_PREF = "loginConfiguration";//$NON-NLS-1$

private static final String CONFIG_DEFAULT = "other";

public static BundleContext getBundleContext()
{
return bundleContext;
}

public static String getConfigurationName()
{
return new DefaultScope().getNode(PLUGIN_ID).get(CONFIG_PREF, CONFIG_DEFAULT);
}

Finally the jaas_config.txt file which defines the RDBMS callback Handler name with the Login Module extension ( RDBMSLoginModule ) should be placed in the plugin's root folder.


RDBMS {
org.eclipse.equinox.security.auth.module.ExtensionLoginModule required
extensionId="com.sample.login.RdbmsLoginModule"
debug=true;
};

No comments: