Building a Custom JBoss Login Module
One of the most commonly used login modules for JBoss is the DatabaseLoginModule, which is designed to allow an application running in JBoss to utilize virtually any table to handle the management of users. With the parameters defined properly it is relatively easy to connect JBoss to your existing user table and allow for single sign-on. But what if you need other steps taken during the sign-in process that go beyond simple username/password verification?
Fortunately, the login modules that are included with JBoss are designed to be extended. In a recent project of mine, I was tasked with modifying the DatabaseLoginModule and extending its functionality to do the following:
- Count the number of failed login attempts
- Lock out a user who exceeded a certain threshold
- Track all failed login attempts
- Record when a user successfully logged in
- Allow a user to be logged in only once
- Reset the login attempts on a successful login
This is a fairly extensive list, and this functionality logically belongs in the login module. To accomplish this goal, I needed to extend the DatabaseLoginModule. This module itself extends the functionality of UsernamePasswordLoginModule, which extends AbstractServerLoginModule. So there are a lot of extensions going on here. First, let us look at the methods that are being used by this string of objects:
convertRawPassword
getRoleSets
getUsersPassword
initialize
createPasswordHash
getCredentials
getIdentity
getUnauthenticatedIdentity
getUsername
getUsernameAndPassword
login
validatePassword
abort
commit
createGroup
getUseFirstPass
logout
Login Method
Fortunately, I will need to extend only a small subset of these methods. To begin the extension, the first method I need to look at is login, which is handled in the UsernamePasswordLoginModule class. The original code for this module from the JBoss 3.2.6 source code is fairly involved, and—based on the way it is designed—does not allow me to interject code into it without modifying the original. The cleanest way to handle this is to copy the code from the UsernamePasswordLoginModule into my extended class and then inject my changes from there. Here is the original login method code:
public boolean login() throws LoginException { // See if shared credentials exist if (super.login() == true) { // Setup our view of the user Object username = sharedState.get("javax.security.auth.login.name"); if (username instanceof Principal) { identity = (Principal) username; } else { String name = username.toString(); try { identity = createIdentity(name); } catch (Exception e) { log.debug("Failed to create principal", e); throw new LoginException("Failed to create principal: " + e.getMessage()); } } Object password = sharedState.get("javax.security.auth.login.password"); if (password instanceof char[]) { credential = (char[]) password; } else if (password != null) { String tmp = password.toString(); credential = tmp.toCharArray(); } return true; } super.loginOk = false; String[] info = getUsernameAndPassword(); String username = info[0]; String password = info[1]; if ((username == null) && (password == null)) { identity = unauthenticatedIdentity; super.log.trace("Authenticating as unauthenticatedIdentity=" + identity); } if (identity == null) { try { identity = createIdentity(username); } catch (Exception e) { log.debug("Failed to create principal", e); throw new LoginException("Failed to create principal: " + e.getMessage()); } // Hash the user entered password if password hashing is in use if (hashAlgorithm != null) { password = createPasswordHash(username, password); } // Validate the password supplied by the subclass String expectedPassword = getUsersPassword(); if (validatePassword(password, expectedPassword) == false) { super.log.debug("Bad password for username=" + username); throw new FailedLoginException("Password Incorrect/Password Required"); } } // Add the username and password to the shared state map if (getUseFirstPass() == true) { sharedState.put("javax.security.auth.login.name", username); sharedState.put("javax.security.auth.login.password", credential); } super.loginOk = true; super.log.trace("User '" + identity + "' authenticated, loginOk=" + loginOk); return true; }