[ Team LiB ] |
Recipe 15.6 Using JAAS to Create a LoginModuleProblemYou want to use the Java Authentication and Authorization Service (JAAS) to create an authentication module that a servlet or JSP can use. SolutionCreate a javax.security.auth.spi.LoginModule class for your application, then store the class under WEB-INF/classes or WEB-INF/lib (in a JAR file). DiscussionThe JAAS is a security API that can be used to create standalone, pluggable authentication or authorization tools for Java applications. Pluggable means that the JAAS security code is not bound to a particular application; it is stored in a JAR file and can be dropped or plugged into web applications and other types of Java programs.
For the sake of clarity, Recipe 15.5-Recipe 15.7 describe a simple example of JAAS authentication that requires two classes, and one servlet that uses the JAAS API. In our examples, these classes are stored in WEB-INF/classes. However, many organizations have a complex security architecture that calls for a more extensive authentication and authorization model, and thus more Java code and objects. In these cases, you'll want to create a separate package name for your JAAS code, archive it in a JAR file, and place it in WEB-INF/lib for web applications to use. Take the following steps to use JAAS for authenticating web clients:
In order to make clearer a rather complex matter, I have broken these steps up into three recipes:
Example 15-9 shows a class that implements the javax.security.auth.spi.LoginModule interface. It performs most of the work in identifying clients, and uses packages that are part of the JAAS API (emphasized with bold in the code sample). You have to make this class available to the servlet engine by placing it in WEB-INF/classes or in a JAR file stored in WEB-INF/lib. Example 15-9. The LoginModule for web authenticationpackage com.jspservletcookbook; import java.util.Map; import java.sql.*; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; import javax.security.auth.spi.LoginModule; import javax.security.auth.*; import javax.security.auth.callback.*; import javax.security.auth.login.*; import javax.sql.*; public class DataSourceLoginModule implements LoginModule { //These instance variables will be initialized by the //initialize( ) method CallbackHandler handler; Subject subject; Map sharedState; Map options; private boolean loginPassed = false; public DataSourceLoginModule( ){}//no-arguments constructor public void initialize(Subject subject, CallbackHandler handler, Map sharedState, Map options){ this.subject = subject; this.handler = handler; this.sharedState = sharedState; this.options = options; } public boolean login( ) throws LoginException { String name = ""; String pass = ""; Context env = null; Connection conn = null; Statement stmt = null; ResultSet rs = null; DataSource pool = null; boolean passed = false; try{ //Create the CallBack array to pass to the //CallbackHandler.handle( ) method Callback[] callbacks = new Callback[2]; //Don't use null arguments with the NameCallback constructor! callbacks[0] = new NameCallback("Username:"); //Don't use null arguments with PasswordCallback! callbacks[1] = new PasswordCallback("Password:", false); handler.handle(callbacks); //Get the username and password from the CallBacks NameCallback nameCall = (NameCallback) callbacks[0]; name = nameCall.getName( ); PasswordCallback passCall = (PasswordCallback) callbacks[1]; pass = new String ( passCall.getPassword( ) ); //Look up our DataSource so that we can check the username and //password env = (Context) new InitialContext( ).lookup("java:comp/env"); pool = (DataSource) env.lookup("jdbc/oracle-8i-athletes"); if (pool == null) throw new LoginException( "Initializing the DataSource failed."); //The SQL for checking a name and password in a table named //athlete String sql = "select * from athlete where name='"+name+"'"; String sqlpass = "select * from athlete where passwrd='"+pass+"'"; //Get a Connection from the connection pool conn = pool.getConnection( ); stmt = conn.createStatement( ); //Check the username rs = stmt.executeQuery(sql); //If the ResultSet has rows, then the username was //correct and next( ) returns true passed = rs.next( ); rs.close( ); if (! passed){ loginPassed = false; throw new FailedLoginException( "The username was not successfully authenticated"); } //Check the password rs = stmt.executeQuery(sqlpass); passed = rs.next( ); if (! passed){ loginPassed = false; throw new FailedLoginException( "The password was not successfully authenticated"); } else { loginPassed = true; return true; } } catch (Exception e){ throw new LoginException(e.getMessage( )); } finally { try{ //close the Statement stmt.close( ); //Return the Connection to the pool conn.close( ); } catch (SQLException sqle){ } } //finally } //login public boolean commit( ) throws LoginException { //We're not doing anything special here, since this class //represents a simple example of login authentication with JAAS. //Just return what login( ) returned. return loginPassed; } public boolean abort( ) throws LoginException { //Reset state boolean bool = loginPassed; loginPassed = false; return bool; } public boolean logout( ) throws LoginException { //Reset state loginPassed = false; return true; } //logout } //DataSourceLoginModule A class that implements LoginModule has to implement the interface's five declared methods: initialize( ) , login( ), commit( ), abort( ), and logout( ). login( ) initiates the main task of checking the username and password and determining whether to successfully authenticate the client. Since this is a simple example, the DataSourceLoginModule focuses on the login( ) method. The other methods in Example 15-9 simply reset the object's state so that it can perform another authentication, although a more complex login process involves other tasks, such as setting up authorization-related objects for the authenticated user.
JAAS separates the responsibility for interacting with the client (such as getting the username and password) and performing authentication into CallbackHandlers and LoginModules, respectively. The LoginModule in Example 15-9 uses a CallbackHandler to get the username and password, then checks this information by accessing a table from an Oracle 8i database. The module uses a JNDI lookup to get access to the database, which Chapter 21 explains in detail. Basically, the LoginModule borrows a Connection from a database-connection pool, uses SQL SELECT statements to check the client's name and password, then returns the Connection to the shared pool by closing it. The CallbackHandler in Example 15-10 gets the client's username and password from HTTP request parameters. The class's constructor includes a ServletRequest argument, from which the class can derive request parameters by calling ServletRequest 's getParameter( ) method. This process will become much clearer when you see how the servlet (see Example 15-11 in Recipe 15.7) uses these classes to perform the authentication. Example 15-10. A CallbackHandler for use in web authenticationpackage com.jspservletcookbook;
import javax.security.auth.callback.*;
import javax.servlet.ServletRequest;
public class WebCallbackHandler implements CallbackHandler {
private String userName;
private String password;
public WebCallbackHandler(ServletRequest request){
userName = request.getParameter("userName");
password = request.getParameter("password");
}
public void handle(Callback[] callbacks) throws java.io.IOException,
UnsupportedCallbackException {
//Add the username and password from the request parameters to
//the Callbacks
for (int i = 0; i < callbacks.length; i++){
if (callbacks[i] instanceof NameCallback){
NameCallback nameCall = (NameCallback) callbacks[i];
nameCall.setName(userName);
} else if (callbacks[i] instanceof PasswordCallback){
PasswordCallback passCall = (PasswordCallback) callbacks[i];
passCall.setPassword(password.toCharArray( ));
} else{
throw new UnsupportedCallbackException (callbacks[i],
"The CallBacks are unrecognized in class: "+getClass( ).
getName( ));
}
} //for
} //handle
}
Just to summarize how the LoginModule and CallbackHandler fit together before you move on to the next two recipes, one of the LoginContext's constructors takes a CallbackHandler as its second parameter, as in the following code: WebCallbackHandler webcallback = new WebCallbackHandler(request); LoginContext lcontext = null; try{ lcontext = new LoginContext( "WebLogin",webcallback ); } catch (LoginException le) { //respond to exception...} Recipe 15.7 shows how to create a JAAS configuration file, which specifies the LoginModule(s) that certain applications will use during authentication. See AlsoSun Microsystems' JAAS developer's guide: http://java.sun.com/j2se/1.4.2/docs/guide/security/jaas/JAASLMDevGuide.html; a list of JAAS tutorials and sample programs: http://java.sun.com/j2se/1.4.2/docs/guide/security/jaas/JAASRefGuide.html; the Javadoc relating to JAAS configuration files: http://java.sun.com/j2se/1.4.1/docs/api/javax/security/auth/login/Configuration.html; Recipe 15.9 on using JAAS with a JSP. |
[ Team LiB ] |