Show Table of Contents
10.5.4. Writing Custom Login Modules
If the login modules bundled with the JBossSX framework do not work with your security environment, you can write your own custom login module implementation that does. Recall from the section on the
JaasSecurityManager architecture that the JaasSecurityManager expected a particular usage pattern of the Subject principals set. You need to understand the JAAS Subject class's information storage features and the expected usage of these features to be able to write a login module that works with the JaasSecurityManager. This section examines this requirement and introduces two abstract base LoginModule implementations that can help you implement your own custom login modules.
You can obtain security information associated with a
Subject in six ways in JBoss using the following methods:
java.util.Set getPrincipals() java.util.Set getPrincipals(java.lang.Class c) java.util.Set getPrivateCredentials() java.util.Set getPrivateCredentials(java.lang.Class c) java.util.Set getPublicCredentials() java.util.Set getPublicCredentials(java.lang.Class c)
For
Subject identities and roles, JBossSX has selected the most natural choice: the principals sets obtained via getPrincipals() and getPrincipals(java.lang.Class). The usage pattern is as follows:
- User identities (username, social security number, employee ID, and so on) are stored as
java.security.Principalobjects in theSubjectPrincipalsset. ThePrincipalimplementation that represents the user identity must base comparisons and equality on the name of the principal. A suitable implementation is available as theorg.jboss.security.SimplePrincipalclass. OtherPrincipalinstances may be added to theSubjectPrincipalsset as needed. - The assigned user roles are also stored in the
Principalsset, but they are grouped in named role sets usingjava.security.acl.Groupinstances. TheGroupinterface defines a collection ofPrincipals and/orGroups, and is a subinterface ofjava.security.Principal. Any number of role sets can be assigned to aSubject. Currently, the JBossSX framework uses two well-known role sets with the namesRolesandCallerPrincipal. TheRolesGroup is the collection ofPrincipals for the named roles as known in the application domain under which theSubjecthas been authenticated. This role set is used by methods like theEJBContext.isCallerInRole(String), which EJBs can use to see if the current caller belongs to the named application domain role. The security interceptor logic that performs method permission checks also uses this role set. TheCallerPrincipalGroupconsists of the singlePrincipalidentity assigned to the user in the application domain. TheEJBContext.getCallerPrincipal()method uses theCallerPrincipalto allow the application domain to map from the operation environment identity to a user identity suitable for the application. If aSubjectdoes not have aCallerPrincipalGroup, the application identity is the same as operational environment identity.
10.5.4.1. Support for the Subject Usage Pattern
To simplify correct implementation of the
Subject usage patterns described in the preceding section, JBossSX includes two abstract login modules that handle the population of the authenticated Subject with a template pattern that enforces correct Subject usage. The most generic of the two is the org.jboss.security.auth.spi.AbstractServerLoginModule class. It provides a concrete implementation of the javax.security.auth.spi.LoginModule interface and offers abstract methods for the key tasks specific to an operation environment security infrastructure. The key details of the class are highlighted in the following class fragment. The JavaDoc comments detail the responsibilities of subclasses.
package org.jboss.security.auth.spi;
/**
* This class implements the common functionality required for a JAAS
* server-side LoginModule and implements the JBossSX standard
* Subject usage pattern of storing identities and roles. Subclass
* this module to create your own custom LoginModule and override the
* login(), getRoleSets(), and getIdentity() methods.
*/
public abstract class AbstractServerLoginModule
implements javax.security.auth.spi.LoginModule
{
protected Subject subject;
protected CallbackHandler callbackHandler;
protected Map sharedState;
protected Map options;
protected Logger log;
/** Flag indicating if the shared credential should be used */
protected boolean useFirstPass;
/**
* Flag indicating if the login phase succeeded. Subclasses that
* override the login method must set this to true on successful
* completion of login
*/
protected boolean loginOk;
// ...
/**
* Initialize the login module. This stores the subject,
* callbackHandler and sharedState and options for the login
* session. Subclasses should override if they need to process
* their own options. A call to super.initialize(...) must be
* made in the case of an override.
*
* <p>
* The options are checked for the <em>password-stacking</em> parameter.
* If this is set to "useFirstPass", the login identity will be taken from the
* <code>javax.security.auth.login.name</code> value of the sharedState map,
* and the proof of identity from the
* <code>javax.security.auth.login.password</code> value of the sharedState map.
*
* @param subject the Subject to update after a successful login.
* @param callbackHandler the CallbackHandler that will be used to obtain the
* the user identity and credentials.
* @param sharedState a Map shared between all configured login module instances
* @param options the parameters passed to the login module.
*/
public void initialize(Subject subject,
CallbackHandler callbackHandler,
Map sharedState,
Map options)
{
// ...
}
/**
* Looks for javax.security.auth.login.name and
* javax.security.auth.login.password values in the sharedState
* map if the useFirstPass option was true and returns true if
* they exist. If they do not or are null this method returns
* false.
* Note that subclasses that override the login method
* must set the loginOk var to true if the login succeeds in
* order for the commit phase to populate the Subject. This
* implementation sets loginOk to true if the login() method
* returns true, otherwise, it sets loginOk to false.
*/
public boolean login()
throws LoginException
{
// ...
}
/**
* Overridden by subclasses to return the Principal that
* corresponds to the user primary identity.
*/
abstract protected Principal getIdentity();
/**
* Overridden by subclasses to return the Groups that correspond
* to the role sets assigned to the user. Subclasses should
* create at least a Group named "Roles" that contains the roles
* assigned to the user. A second common group is
* "CallerPrincipal," which provides the application identity of
* the user rather than the security domain identity.
*
* @return Group[] containing the sets of roles
*/
abstract protected Group[] getRoleSets() throws LoginException;
}
You'll need to pay attention to the
loginOk instance variable. This must be set to true if the login succeeds, false otherwise by any subclasses that override the login method. Failure to set this variable correctly will result in the commit method either not updating the subject when it should, or updating the subject when it should not. Tracking the outcome of the login phase was added to allow login modules to be chained together with control flags that do not require that the login module succeed in order for the overall login to succeed.
The second abstract base login module suitable for custom login modules is the
org.jboss.security.auth.spi.UsernamePasswordLoginModule. This login module further simplifies custom login module implementation by enforcing a string-based username as the user identity and a char[] password as the authentication credentials. It also supports the mapping of anonymous users (indicated by a null username and password) to a principal with no roles. The key details of the class are highlighted in the following class fragment. The JavaDoc comments detail the responsibilities of subclasses.
package org.jboss.security.auth.spi;
/**
* An abstract subclass of AbstractServerLoginModule that imposes a
* an identity == String username, credentials == String password
* view on the login process. Subclasses override the
* getUsersPassword() and getUsersRoles() methods to return the
* expected password and roles for the user.
*/
public abstract class UsernamePasswordLoginModule
extends AbstractServerLoginModule
{
/** The login identity */
private Principal identity;
/** The proof of login identity */
private char[] credential;
/** The principal to use when a null username and password are seen */
private Principal unauthenticatedIdentity;
/**
* The message digest algorithm used to hash passwords. If null then
* plain passwords will be used. */
private String hashAlgorithm = null;
/**
* The name of the charset/encoding to use when converting the
* password String to a byte array. Default is the platform's
* default encoding.
*/
private String hashCharset = null;
/** The string encoding format to use. Defaults to base64. */
private String hashEncoding = null;
// ...
/**
* Override the superclass method to look for an
* unauthenticatedIdentity property. This method first invokes
* the super version.
*
* @param options,
* @option unauthenticatedIdentity: the name of the principal to
* assign and authenticate when a null username and password are
* seen.
*/
public void initialize(Subject subject,
CallbackHandler callbackHandler,
Map sharedState,
Map options)
{
super.initialize(subject, callbackHandler, sharedState,
options);
// Check for unauthenticatedIdentity option.
Object option = options.get("unauthenticatedIdentity");
String name = (String) option;
if (name != null) {
unauthenticatedIdentity = new SimplePrincipal(name);
}
}
// ...
/**
* A hook that allows subclasses to change the validation of the
* input password against the expected password. This version
* checks that neither inputPassword or expectedPassword are null
* and that inputPassword.equals(expectedPassword) is true;
*
* @return true if the inputPassword is valid, false otherwise.
*/
protected boolean validatePassword(String inputPassword,
String expectedPassword)
{
if (inputPassword == null || expectedPassword == null) {
return false;
}
return inputPassword.equals(expectedPassword);
}
/**
* Get the expected password for the current username available
* via the getUsername() method. This is called from within the
* login() method after the CallbackHandler has returned the
* username and candidate password.
*
* @return the valid password String
*/
abstract protected String getUsersPassword()
throws LoginException;
}
The choice of subclassing the
AbstractServerLoginModule versus UsernamePasswordLoginModule is simply based on whether a string-based username and credentials are usable for the authentication technology you are writing the login module for. If the string-based semantic is valid, then subclass UsernamePasswordLoginModule, otherwise subclass AbstractServerLoginModule.
The steps you are required to perform when writing a custom login module are summarized in the following depending on which base login module class you choose. When writing a custom login module that integrates with your security infrastructure, you should start by subclassing
AbstractServerLoginModule or UsernamePasswordLoginModule to ensure that your login module provides the authenticated Principal information in the form expected by the JBossSX security manager.
When subclassing the
AbstractServerLoginModule, you need to override the following:
void initialize(Subject, CallbackHandler, Map, Map): if you have custom options to parse.boolean login(): to perform the authentication activity. Be sure to set theloginOkinstance variable to true if login succeeds, false if it fails.Principal getIdentity(): to return thePrincipalobject for the user authenticated by thelog()step.Group[] getRoleSets(): to return at least oneGroupnamedRolesthat contains the roles assigned to thePrincipalauthenticated duringlogin(). A second commonGroupis namedCallerPrincipaland provides the user's application identity rather than the security domain identity.
When subclassing the
UsernamePasswordLoginModule, you need to override the following:
void initialize(Subject, CallbackHandler, Map, Map): if you have custom options to parse.Group[] getRoleSets(): to return at least oneGroupnamedRolesthat contains the roles assigned to thePrincipalauthenticated duringlogin(). A second commonGroupis namedCallerPrincipaland provides the user's application identity rather than the security domain identity.String getUsersPassword(): to return the expected password for the current username available via thegetUsername()method. ThegetUsersPassword()method is called from withinlogin()after thecallbackhandlerreturns the username and candidate password.

Where did the comment section go?
Red Hat's documentation publication system recently went through an upgrade to enable speedier, more mobile-friendly content. We decided to re-evaluate our commenting platform to ensure that it meets your expectations and serves as an optimal feedback mechanism. During this redesign, we invite your input on providing feedback on Red Hat documentation via the discussion platform.