The default JRun security mechanism described in the previous section might not be suitable for your site. For example, the input parameters for the JDBC, LDAP, and Windows login modules might not work with your existing security system.
In this case, you can customize and extend JRun security to meet your needs, by creating one or more customized login modules.
Before customizing JRun security, it is important to understand how JRun uses login modules. In a default JAAS implementation, login modules are used for authentication alone. However, JRun uses login modules for both authentication and authorization, as follows:
login method, populating the javax.security.auth.callback.NameCallback object with the user name and the PasswordCallback object with the password.
login method, populating the jrun.security.RolesCallback object with the user name and a Collection object that contains the list of roles allowed to access a secured resource. The RolesCallback object is specific to the JRun security implementation; it is not part of JAAS.
You specify the processing to be performed by passing either USER or ROLE in the mode attribute specified in the auth.config file. For more information on handling this attribute, see the sample code.
The following sample code shows how a login module accesses user name and password:
...
NameCallback n = new NameCallback("User Name - ", "Guest");
PasswordCallback p = new PasswordCallback("Password - ", false);
Callback[] callbacks = {n, p};
try {
cbHandler.handle(callbacks);
username = n.getName().trim();
char[] tmpPassword = p.getPassword();
...
The following sample code accesses user name and permissible roles:
...
boolean userRoleFound = false;
RolesCallback rcb = new RolesCallback();
Callback[] callbacks = {rcb};
try {
// cbHandler is the CallbackHandler object
��cbHandler.handle(callbacks);
Principal p = rcb.getPrincipal();
username = p.getName().trim();
Collection rolesToCheck = rcb.getRoles();
...
Most sites can use the default JRun JDBC login module for authentication and authorization. However, if your site cannot use the default JRun JDBC login module, you can write a customized login module to access users and roles from your site-specific relational database.
For more information on the default JDBC login module, see "Using a JDBC-based security implementation".
To use a customized JDBC login module, perform the following steps:
login method manages user authentication and role authorization by accessing a relational database. For an example, see SampleJDBCLoginModule, found in the samples/SERVER-INF/lib/jrun/samples/security directory.
SampleJDBCLoginModule. You can also make this specification through the JMC.The following example shows a customized JDBC login module:
...
// Required imports
import javax.security.auth.spi.LoginModule;
import javax.security.auth.Subject;
import javax.security.auth.login.LoginException;
import javax.security.auth.login.FailedLoginException;
import javax.security.auth.callback.*;
import java.security.Principal;
// Imports used for login module-specific functionality.
// Imports used for login module-specific (JDBC) functionality.
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
import java.util.Map;
import java.util.Collection;
import java.util.Iterator;
import java.util.ArrayList;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
// JRun-specific imports
import jrun.security.SimplePrincipal;
import jrun.security.RolesCallback;
// Used for resource bundle processing.
import jrunx.util.RB;
// Used for logging.
import jrunx.kernel.JRun;
public class SampleJDBCLoginModule implements LoginModule {
private Subject subject;
private CallbackHandler cbHandler;
private Map sharedState;
private Map options;
/**
* For mode = "USER"
* SQL statement to query the password for a specific username
* for example: "select PasswordColumn from UsersTable where UserName=?"
*
* For mode = "ROLE"
* SQL statement to query the roles for a particular username
* eg: "select RoleColumn from RolesTable where UserName=?"
*/
private String queryString=null;
/**
* The user can either specify the sql statement to retrieve the username and password
* taking the username as the input or can specify the tablename, username, column
* and password column
* Specified as a parameter in auth.config.
*/
private String userColumn=null;
private String passwordColumn=null;
private String tableName=null;
private String roleColumn=null;
/**
* The userId and password to access the database containing the user security information
* Specified as a parameter in auth.config.
*/
private String dbUserId=null;
private String dbPassword=null;
private String username=null;
private char[] password=null;
/**
* The JDBC Data Source to look up in JNDI to get to the database
* This can be a JRun data source.
* Specified as a parameter in auth.config.
*/
String dsJndiName=null;
// the authentication status
private boolean succeeded = false;
private boolean commitSucceeded = false;
//authenticated principal object
private SimplePrincipal userPrincipal;
// Login mode can be "USER" or "ROLE" to indicate whether user credential
// is to be authenticated or the user role is to be authorized.
protected String loginMode = "USER";
/**
* <p>The JRun security manager calls the initialize method
* at the beginning of an authentication
* or authorization request.
*/
public void initialize(Subject subj, CallbackHandler cbh, Map sharedState, Map options) {
this.subject = subj;
this.cbHandler = cbh;
this.sharedState = sharedState;
this.options = options;
loginMode = (String) options.get("mode");
dbUserId = (String) options.get("databaseUserId");
dbPassword = (String) options.get("databasePassword");
dsJndiName = (String) options.get("dataSourceJNDIName");
queryString = (String) options.get("queryString");
tableName = (String) options.get("tableName");
userColumn = (String) options.get("userNameColumn");
if(loginMode.equals("USER") )
// Parameters specific to user authentication.
passwordColumn = (String) options.get("passwordColumn");
else // Parameters specific to user-role authorization
roleColumn = (String) options.get("roleColumn");
JRun.getLogger().logInfo("*** SampleJDBCLoginModule: initialize");
}
/**
* <p>The JRun security manager calls the login method after calling
* the initialize method for an authentication
* or authorization request.
*/
public boolean login() throws LoginException {
if(cbHandler == null) {
throw new LoginException(RB.getString(SampleJDBCLoginModule.class,
"SampleJDBCLoginModule.noCallBackHandlerAvailable") );
}
JRun.getLogger().logInfo("*** SampleJDBCLoginModule: login. Mode is: " + loginMode);
if (loginMode.equals("ROLE") ) {
// Call this method for authorization.
return validateRole();
}
else {
// default
// Call this method for authentication.
return loginUser();
}
}
/**
* <p> Called when mode=USER
*/
protected boolean loginUser() throws LoginException {
NameCallback n = new NameCallback("User Name - ", "Guest");
PasswordCallback p = new PasswordCallback("Password - ", false);
Callback[] callbacks = {n, p};
try {
cbHandler.handle(callbacks);
// Get passed username NameCallback.
username = n.getName().trim();
// Debugging aid.
JRun.getLogger().logInfo("*** loginUser(): user logging in is: " + username);
// Get passed password from PasswordCallback.
char[] tmpPassword = p.getPassword();
if( tmpPassword != null ) {
password = new char[tmpPassword.length];
System.arraycopy(tmpPassword, 0, password, 0, tmpPassword.length);
p.clearPassword();
}
}
catch(java.io.IOException e) {
throw new LoginException(e.toString());
}
catch(UnsupportedCallbackException uce) {
// throw new LoginException("Unsupported Callback: " + uce.getCallback());
throw new LoginException(RB.getString(SampleJDBCLoginModule.class,
"SampleJDBCLoginModule.unsupportedCallBack"));
}
//Validate the password from callback with the password from the database
String passwordFromDB = getUserPassword().trim();
JRun.getLogger().logInfo("*** loginUser(): password from db is: " + passwordFromDB);
succeeded = true;
char[] pw = passwordFromDB.toCharArray() ;
if(password.length == pw.length) {
for(int i = 0; i < pw.length; i++) {
if (password[i] != pw[i]) {
succeeded = false;
break;
} // end inner if
} // end for
}
else
succeeded = false; // not the same length
return succeeded;
}
/**
* <p> Called when mode=ROLE
*/
protected boolean validateRole() throws LoginException {
boolean userRoleFound = false;
JRun.getLogger().logInfo("*** validateRole()");
RolesCallback rcb = new RolesCallback();
Callback[] callbacks = {rcb};
try {
cbHandler.handle(callbacks);
// Get username from RolesCallback.
Principal p = rcb.getPrincipal();
username = p.getName().trim();
// Get authorized roles from RolesCallback.
Collection rolesToCheck = rcb.getRoles();
// Get assigned roles from database.
ArrayList rolesFromDatabase = getUserRoles();
//Iterate over rolesToCheck and find one in the rolesFromDataBase with the username
Iterator i = rolesToCheck.iterator();
while(i.hasNext() && !userRoleFound) {
String thisRoleName = (String) i.next();
int numberOfRolesFromDB = rolesFromDatabase.size();
for(int index = 0; index < numberOfRolesFromDB; index++) {
String dbRoleName = (String) rolesFromDatabase.get(index);
if(thisRoleName.equals(dbRoleName.trim()) ) {
succeeded = true;
userRoleFound = true;
} // end inner if
} // end for
} // end while
}
catch(java.io.IOException e) {
throw new LoginException(e.toString());
}
catch(UnsupportedCallbackException uce) {
throw new LoginException(RB.getString(SampleJDBCLoginModule.class,
"SampleJDBCLoginModule.unsupportedCallBack") );
}
catch(Exception e) {
JRun.getLogger().logError(RB.getString(SampleJDBCLoginModule.class,
"SampleJDBCLoginModule.errorValidatingRole"), e);
}
return userRoleFound;
}
/*
* <p>Database call to retrieve password for a user name.
*/
protected String getUserPassword() throws LoginException {
Connection conn = null;
PreparedStatement ps = null;
String passwordFromResult=null;
try {
InitialContext ctx = new InitialContext();
DataSource ds = (DataSource) ctx.lookup(dsJndiName);
if(dbUserId != null)
ds.getConnection(dbUserId, dbPassword);
else
conn = ds.getConnection();
// The user can either provide a SQL query string or the tablename,
// password and username columns
if(queryString != null) {
ps = conn.prepareStatement(queryString);
ps.setString(1, username);
}
else {
queryString = "select " + passwordColumn + " from " + tableName +
" where " + userColumn + "=" + username;
ps = conn.prepareStatement(queryString);
}
ResultSet rs = ps.executeQuery();
if( rs.next() == false )
throw new FailedLoginException(RB.getString(SampleJDBCLoginModule.class,
"SampleJDBCLoginModule.userNameNotFound") );
passwordFromResult = rs.getString(1);
rs.close();
}
catch(NamingException ex) {
throw new LoginException(ex.toString(true));
}
catch(SQLException ex) {
throw new LoginException(ex.toString());
}
finally {
connCleanup(ps, conn);
}
return passwordFromResult;
}
/**
* Call database to retrieve assigned roles for a user name.
*/
protected ArrayList getUserRoles() throws LoginException {
Connection conn = null;
PreparedStatement ps = null;
ArrayList rolesFromDB= new ArrayList();
try {
InitialContext ctx = new InitialContext();
DataSource ds = (DataSource) ctx.lookup(dsJndiName);
if(dbUserId != null)
ds.getConnection(dbUserId, dbPassword);
else
conn = ds.getConnection();
// In the auth.config file, the user can either provide a SQL query
// string or the tablename, rolename and username columns
if(queryString != null) {
ps = conn.prepareStatement(queryString);
ps.setString(1, username);
}
else {
queryString = "select " + roleColumn + " from " + tableName +
" where " + userColumn + "=" + username;
ps = conn.prepareStatement(queryString);
}
ResultSet results = ps.executeQuery();
String dbRolename=null;
if(results.getFetchSize() > 0) {
while(results.next() ) {
dbRolename = results.getString(1);
rolesFromDB.add(dbRolename);
} // end while
} // end if
else
throw new FailedLoginException(RB.getString(SampleJDBCLoginModule.class,
"SampleJDBCLoginModule.userNameNotFound") );
results.close();
} // end try
catch(NamingException ex) {
throw new LoginException(ex.toString(true));
}
catch(SQLException ex) {
throw new LoginException(ex.toString());
}
finally {
connCleanup(ps, conn);
}
return rolesFromDB;
}
protected void connCleanup(PreparedStatement ps, Connection conn) {
if( ps != null ) {
try {
ps.close();
}
catch(SQLException e)
{}
}
if( conn != null ) {
try {
conn.close();
}
catch (SQLException ex)
{}
}
}
/**
* <p>Indicates whether authentication or authorization succeeded.
*/
public boolean commit() throws LoginException {
if (succeeded == false) {
return false;
}
else {
// add a Principal (authenticated identity)
// to the Subject
// assume the user we authenticated is the SimplePrincipal
userPrincipal = new SimplePrincipal(username);
if (!subject.getPrincipals().contains(userPrincipal))
subject.getPrincipals().add(userPrincipal);
// in any case, clean out state
username = null;
//for (int i = 0; i < password.length; i++)
//password[i] = ' ';
password = null;
commitSucceeded = true;
return true;
}
}
/**
* <p> This method is called if the LoginContext's
* overall authentication failed.
* (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL LoginModules
* did not succeed).
*
* <p> If this LoginModule's own authentication attempt
* succeeded (checked by retrieving the private state saved by the
* <code>login</code> and <code>commit</code> methods),
* then this method cleans up any state that was originally saved.
*
* <p>
*
* @exception LoginException if the abort fails.
*
* @return false if this LoginModule's own login and/or commit attempts
* failed, and true otherwise.
*/
public boolean abort() throws LoginException {
if (succeeded == false) {
return false;
}
else if (succeeded == true && commitSucceeded == false) {
// login succeeded but overall authentication failed
succeeded = false;
username = null;
if (password != null) {
password = null;
}
userPrincipal = null;
}
else {
// overall authentication succeeded and commit succeeded,
// but someone else's commit failed
logout();
}
return true;
}
/**
* Logout the user.
*
* <p> This method removes the <code>SamplePrincipal</code>
* that was added by the <code>commit</code> method.
*
* <p>
*
* @exception LoginException if the logout fails.
*
* @return true in all cases since this <code>LoginModule</code>
* should not be ignored.
*/
public boolean logout() throws LoginException {
subject.getPrincipals().remove(userPrincipal);
username = null;
if (password != null) {
for (int i = 0; i < password.length; i++)
password[i] = ' ';
password = null;
}
userPrincipal = null;
return true;
}
} // end class
RSS feed | Send me an e-mail when comments are added to this page | Comment Report
Current page: http://livedocs.adobe.com/jrun/4/JRun_Administrators_Guide/authentic5.htm
Comments
rnielsen said on Oct 1, 2002 at 12:58 PM : emilesvt said on Jan 12, 2005 at 7:55 AM :