This appendix describes an abstract class used for writing pluggable authentication modules.
This API is classified Unstable. For an explanation of this classification, see the manpage attributes(5) in Solaris 2.6. The likelihood that this API will change is very high. Make a backup copy of any file that you are going to edit.
This is an abstract class that can be used to write pluggable authentication modules. Because it is an abstract class authentication, writers must subclass and override the abstract methods init, validate, and getUserTokenId.
The HTML for the authentication states is generated dynamically based on the parameters set in the configuration file for the authentication module developed. There must be a configuration file with the name of the class (no package name) and the extension .properties. This file must reside in /etc/opt/SUNWstnr on the i-Planet server when it is started.
The properties file is of the following form shown in Missing Cross Reference Target
SCREEN TIMEOUT 60 TEXT Sample Login Page TOKEN Enter User Name: PASSWORD Enter User Password: SCREEN TIMEOUT 30 TEXT Sample Login Page 2 TOKEN Enter Favorite Color TOKEN Enter Secret Pin Number PASSWORD Enter Challenge form |
Each SCREEN entry corresponds to one authentication state or authentication HTML page. When an authentication session is invoked, one HTML page is sent for each state. In the previous sample SCREENS, the first state sends an HTML page asking the users to enter a token and a password. When the users submit the token and the password, the validate() method is called. Module writers get the tokens, validate them, and return them. The second page is then sent and the validate() routine is again called.
If the module writers throw a LoginException, an authentication failed page is sent to the user. If no exception is thrown, which implies successful completion, the users are redirected to their default page. The TIMEOUT parameter is used to ensure that the users respond in a timely manner. If the time between sending the page in response is greater than the TIMEOUT, a time-out page is sent.
There are optional HTML and IMAGE parameters for each page. The HTML parameter allows the module writers to use their own HTML page for the authentication screens. The IMAGE parameter allows writers to display a background image on each page.
The SetReplace method allows module writers to substitute dynamic text for the token and password accompanying text descriptions.
When multiple pages are sent to the user, the tokens from a previous page may be retrieved by using the getTokenForState methods. Each page is referred to as a state. The underlying authentication module keeps the tokens from the previous states until the authentication is completed.
For the i-Planet server to recognize your authentication module, you must add the full class name with package to the /etc/opt/SUNWstnr/platform.conf file after the authenticators = parameter. The reverseproxy.policy file on the i-Planet gateway must also be modified to allow requests to your authentication module. You will find this file on the i-Planet gateway in the directory /opt/SUNWsnrp/policy/. You must restart the reverse proxy server on the i-Planet gateway and the web server on the i-Planet server after you have modified these files.
Each authentication session creates a new instance of your authentication Java class. The reference to the class is released once the authentication session has either succeeded or failed.
Any static data or reference to any static data in your authentication module must be thread safe.
For example, .properties and Java module files, see "Sample Files"" section in this appendix or the /opt/SUNWstnr/sample/auth/com/sun/login/sample on the i-Planet server. /opt is the directory for the default installation. If you have customized your installation, you may have installed this file in another directory.
public Login () throws LoginException |
If the Login constructor fails, LoginException should be thrown.
public abstract void init() throws LoginException |
This method must be overridden. It is called each time an authentication session is started. If the initialization of the module fails, the LoginException should be thrown.
Overrides: init in class Authenicator
public abstract void validate() throws LoginException |
This method must be overridden. It is called once for each authentication page that is specified in the authentication modules properties file. The various getToken methods may be used to get the values for the user-entered tokens and passwords. LoginException should be thrown at some point during the validate() method if authentication has failed. The message in the exception is logged and users are sent an Authentication Failed page. If no exception is thrown, which implies successful completion, and all authentication pages have been sent, the user is authenticated. The abstract method getUserTokenId() is called to get the authenticated name of the user.
Overrides: validate in class Authenticator
public abstract String getUserTokenId() |
This method must overridden. It is called once after the all authentication pages have been sent to the user.
Overrides: getUserTokenId in class Authenticator
public String getSessionId() |
This method returns a unique key for this authentication session.
public int getCurrentState() |
This method returns the current state in the authentication process.
public int getNumberOfTokens() |
This method returns the total number of tokens and passwords in the current authentication state.
public int getNumberOfTokensForState(int stateNumber) |
This method returns the total number of tokens and passwords for the given authentication state. This method may be used to get token values from previous authentication states.
public String getToken(int index) |
This method returns the user-entered value for the specified token in the current authentication state.
public String[] getToken() |
This method returns the user-entered value for the first token in the current authentication state.
public String[] getAllTokens() |
This method returns all the user-entered tokens in the current authentication state.
public String[] getAllTokensForState(int stateNumber) |
Returns all the user-entered tokens in the specified authentication state.
public int getNumberOfStates() |
This method returns the number of authentication states for this authentication module.
public void setReplaceText(int token, String text) |
The tokens and passwords have text descriptions for each authentication page. If your module needs to add to these descriptions, this can be done by inserting the keyword into the description. This method can then be used to substitute the specified text with generated dynamic text.
This method should be called for the next state, before returning from the validate method().
public void setReplaceText(int token, String text[]) |
Same as setReplaceText(), but allows replacement of multiple text strings.
public void logout() |
Not implemented. This method is a placeholder. It will be called when a user logs out.
This section is a task-oriented guide to writing a pluggable authentication module. It takes you through the steps for writing a pluggable authentication module. At the end of this section is a sample. You must first decide what your authentication mechanism is going to be and how many pages it will be and what inputs that the user will have to enter for each page.
You must first write a stand-alone Java class that will call your specific authentication process, library, or the interface that it requires. In many cases, this will require the Java Native Interface (JNI) to have access to C or C++ library or system call.
You will most likely save time if you get it working in a stand-alone environment before you integrate it into i-Planet.
Write a stand-alone Java class that will call your specific authentication process, library, or the interface that it requires.
Test your module in a stand-alone environment.
Assume you have a Java class called com.companyx.auth.MyLogin that takes two inputs on the command line from a user. One input is a userId and the second is a password. MyLogin then passes these two inputs to two routines called myAuthenticateId(Id) and myAuthenticatePass(pass), which in turn calls the authentication-specific library and returns a success or fail with an error message if it fails.
After you have written your pluggable authentication module and tested it, you must integrate it into i-Planet. Use the following procedure to integrate your module into i-Planet.
Modify your class to do the following:
import com.sun.authd.* extend com.sun.authd.Login implement the validate(), init(), and getUserTokenId() methods |
The validate method replaces your input gathering method. Each time the user submits an HTML page, the validate() method will be called. In the method, you call your authentication-specific routines. At any point in this method, if the authentication has failed, you must throw a LoginException. If desired, you can pass the reason for failure as an argument to the exception. This reason will be logged in the i-Planet authentication log.
init() should be used if your class has any specific initialization such as loading a JNI library. init() is called once for each instance of your class. Every authentication session creates a new instance of your class. Once a login session is completed the reference to the class is released.
getUserTokenId() is called once at the end of a successful authentication session by the i-Planet authentication server. This is the string the authenticated user will be known as in the i-Planet server. A login session is deemed successful when all pages in the MyLogin.properties file have been sent and your module has not thrown an exception.
Create a MyLogin.properties file.
This file contains some simple directives which tell the i-Planet authentication daemon how to create the HTML pages for your login class dynamically. Since MyLogin requires two screens with one input each, the MyLogin.properties file will look like the following:
SCREEN TEXT Welcome to my login pages TIMEOUT 60 TOKEN Please enter your company ID SCREEN TIMEOUT 120 TEXT Welcome to my second page PASSWORD Please enter your password |
This .properties file tells the i-Planet authentication daemon to send two successive pages to the user. After each submit, your MyLogin validate routine will be called with the inputs made available through public getXX methods of the Login class.
Compile your java class.
Include /opt/SUNWjeev/classes/authd.jar and /opt/SUNWjeev/classes/acm.jar in your CLASSPATH.
If you use a package name to create the directories for the package, note the name that you used.
Copy your class file to /opt/SUNWjeev/classes.
If you use a jar file, you will need to edit the /opt/SUNWjeev/bin/iplsrv script and add your jar file to the web server's CLASSPATH. You can also just add it to your root CLASSPATH. The iplanet_srv script will pick it up.
If you have JNI library, you must copy it into /opt/SUNWjeev/lib/sparc, or you will need to modify the LD_LIBRARY_PATH of iplsrv script.
Copy your MyLogin.properties file to /etc/opt/SUNWstnr.
Add your full package.class name to the authenticators property in the platform.conf file.
authenticators=com.sun.login.unix.Unix com.companyx.auth.MyLogin
Add the lines to the /opt/SUNWsnrp/policy/reverseproxy.policy file on the i-Planet gateway.
http://host:port/login/MyLogin
https://host/login/MyLogin
Be sure to add both http and https.
Restart the web server on the i-Planet server.
Restart the reverse proxy server on the i-Planet gateway.
Test your login.
Missing Cross Reference Target contains a sample Java file for MyLogin Module.
package com.companyx.auth; import com.sun.authd.*; public class MyLogin extends Login { private String userTokenId; public MyLogin() throws LoginException{} public void init() throws LoginException {} public void validate() throws LoginException { String token = getToken(); if (getCurrentState() == 1) { int ret = myAuthenticateId(token); if (ret == 0) { throw new LoginException("Invalid UserId: " + userTokenId); } } |
else { int ret = myAuthenticatePassword(token); if (ret == 0) { throw new LoginException("Invalid Password: " + userTokenId); } userTokenId = token; } } public String getUserTokenId() { return userTokenId; } public int myAuthenticateID( Sting userID ) { return 1; } public int myAuthenticatePassword( String userId ) { return 1; } } |
There is also a sample in /opt/SUNWstnr/sample/auth/com/sun/login that uses most of the methods in the Login class. There is also a javadoc in /opt/SUNWstnr/docs/javadocs/com/sun/authd for the Login class.
Each authentication module must have a corresponding module.properties file.The following sample files show the form and content of the .properties file and samples of the code.
There are two examples of .properties and Java code files.
Missing Cross Reference Target is a sample .properties file.
SCREEN TEXT This is a sample login page TOKEN First Name TOKEN Last Name TOKEN Favorite Car PASSWORD Favorite Car's password SCREEN TIMEOUT 200 TEXT The all password page PASSWORD password 1 PASSWORD password 2 PASSWORD password 3 PASSWORD password 4 SCREEN TEXT 3rd page TOKEN Enter anything TOKEN This will be your userID |
Missing Cross Reference Target is the sample code for MySampleLogin.java.
package com.sun.login.sample; import java.util.*; import com.sun.authd.*; public class MySampleLogin extends Login { private String userTokenId; public MySampleLogin() throws LoginException{ System.out.println("MySampleLogin()"); } public void init() throws LoginException { System.out.println("MySampleLogin initialization"); } public void validate() throws LoginException { System.out.println("SampleLoginModule validate()"); // get the current login page int login_page = getCurrentState(); // print out all the tokens and passwords the user entered String[]tokens = getAllTokens(); for (int i = 0; i < tokens.length; i++) { System.out.println(i + "->" + " " + tokens[i]); } if (login_page == 2) { userTokenId = new String(getToken(1)); } } public String getUserTokenId() { return new String(userTokenId); } public String getDescription() { return "My Sample Login Module"; } } |
You can also find the following sample file SampleLoginModule.properties in /opt/SUNWstnr/sample/auth/com/sun/login/sample, where /opt is the directory in which it is installed by default. Missing Cross Reference Target shows a sample .properties file.
SCREEN TEXT This is a sample login page TOKEN First Name TOKEN Last Name SCREEN TIMEOUT 30 TEXT You made it to page 2 PASSWORD Enter any password SCREEN TIMEOUT 60 TEXT You made it past the first page TOKEN Enter <REPLACE>'s favorite car PASSWORD Enter <REPLACE>'s favorite color SCREEN TEXT 4th page PASSWORD who cares TOKEN anything here |
You can also find the following sample in a file named SampleLoginModule.java in /opt/SUNWstnr/sample/auth/com/sun/login/sample, where /opt is the directory in which it is installed by default.Missing Cross Reference Target shows a sample Java module
package com.sun.login.sample; import java.util.*; import com.sun.authd.*; public class SampleLoginModule extends Login { private String userTokenId; private String firstName; private String lastName; public SampleLoginModule() throws LoginException{ System.out.println("SampleLoginModule()"); } public void init() throws LoginException { System.out.println("SampleLoginModule initialization"); } public void validate() throws LoginException { int currentState = getCurrentState(); if (currentState == 1) { firstName = getToken(1); lastName = getToken(2); if (firstName.equals("") || lastName.equals("")) { throw new LoginException("Sample failed names must not be\ empty"); } return; } else if (currentState == 2) { String pass = getToken(1); System.out.println("Replace Text first: " + firstName + " last: "\ + lastName); setReplaceText(1, firstName); setReplaceText(2, lastName); return; } |
else if (currentState == 3) { String[] tokens = getAllTokens(); for (int i=0; i<getNumberOfTokens(); i++) { System.out.println("Token-> " + tokens[i]); } return; } else if (currentState == 4) { String[] tokens = getAllTokensForState(1); for (int i=0; i<getNumberOfTokensForState(1); i++) { System.out.println("Token-> " + tokens[i]); } } userTokenId = firstName; } public String getUserTokenId() { return userTokenId; } } |