ProblemYou 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 CallbackHandler s and LoginModule s, 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 8 i 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 authentication package 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. ![]() |