Many people who deploy SJS Access Manager first time often want to customize the default login page. It could be piece of cake if you only need to change the UI Layout or swap some canned image file. However, most real-life deployment are not so straightforward. I wish this blog provides you more knowledge on such tasks. Well, SJS Access Manager now is open sourced as OpenSSO Project and the move makes thing easier because we can look into Login Module source.
Let's talk about some basic thing, in case you don't know yet.
Authentication Module and Chainning
SJS Access Manager (AM) is designed to be able to support multiple authentication modules. When a user login against AM, he in fact authenticate against an Authentication Chain. An authentication chain consists of one or more than one Authentication Modules. To setup an authentication chain, you need to select a few modules, define their processing order, and assign each module's significance to entire authentication result. The "significance" I mentioned is called Condition. There are four Condition options you can assign to an authentication module:
- SUFFICIENT : A successful authentication to this module means successful authentication to the chain. Follow-up authentication modules in the chain WILL NOT be proceed. A failed authentication attempt to this module doesn't mean failure of chain and AM WILL process the follow-up modules to determine the final result.
- REQUIRED : An attempt to authenticate this module MUST be successful. However, AM WILL STILL process the follow-up modules even a failed authentication attempt occurs. In addition, although this module is required to pass, a user MAY NOT need to authenticate to this module if he already successfully authenticate to a SUFFICIENT module earlier.
- REQUISITE : An attempt to authenticate this module MUST be successful. If a failure authentication attempt to this module occurs, AM WILL NOT process the follow-up modules and the chain result is failed. In addition, although this module is required to pass, a user MAY NOT need to authenticate to this module if he already successfully authenticate to a SUFFICIENT module earlier.
- OPTIONAL : I don't know how to describe this type of condition. AM always process the follow-up modules no matter the authentication result to this module.
Let's create an authentication chain like this :
The snapshot from AM console shows an authentication chain having three authentication modules: LDAP, LocalMySQL, and LocalSolaris. They are a instance of built-in LDAP, JDBC, and Unix authentication module respectively. When a user try to authenticate to this chain, he will see LDAP login page first. He is lucky to pass this LDAP authentication, then go straight into the amconsole (or a welcome page). AM won't show him MySQL and Solaris credentials because LDAP is configured as SUFFICIENT. On the other hand, if he failed to login to LDAP, he will be prompt by LocalMySQL login page. He has to pass LocalMySQL authentication because it is a REQUISITE module, otherwise the login to chain will fail.
OK, assume he pass LocalMySQL login, he will see LocalSolaris login page and he has to pass it as well. At this point, the result of LocalSolaris authentication will determine the result of the entire authentication chain.
What is the very first authentication chain you need to login to before creating any other authentication chain ? The Access Manager installer create a default authentication chain called "ldapService" for the use. This default chain only contains one authentication module -- LDAP (or DataStore if you install OpenSSO with local file repository). That's what you usually authenticate to. One important thing is AM separates the login handler for admin console from the one for /UI/Login. As the following picture shown, the Administrator Authentication Chain configuration is for admin console access (/amserver/console); the Default Authentication Chain configuration is for access Login servlet (/amserver/UI/Login). AM installer assigns both to ldapService chain and I altered the upper one to my own chain. My advice is to leave Administrator Authentication Chain unchanged. A bad assignment will result amadmin lockout, be careful.
Login process for a single Module
Now we're going to see how AM navigate login page and the process flow for credential validation.
The diagram on the bottom tells the trick. Before talking about the diagram, you should understand the nut & bolt of AM Authentication Module : Authentication Module Class, UI Callback XML, and UI Callback Template. Here are the details.
Authentication Module Class
It is a Java program. One authentication module typically has 2-3 java classes. The am_services.jar file contains the classes of all built-in Authentication Modules. We usually don't need to touch them (and better not to). It execute the logic to validate credentials given by user, and also callback to UI component when more information needed from user. An authentication module class is a subclass of com.sun.identity.authentication.spi.AMLoginModule, and certainly need to implements the necessary methods such as init( ), process( ), and getPrintcipal( ). AM creates a instance of the module class for each authentication request to this module.
UI Callback XML
Every authentication module should have its own UI Callback XML file, even though it is a zero size file (like Certificate login module). Callback is the fundamental concept brought in by JAAS (Java Authentication Authorization Service). It assume the authentication process may be complex and involve multiple client-server interaction. Server can use callback to request more credential input from client when necessary and the callback question may change on the fly by server due to the result from previous callback. OK, it is just...complex...but flexible.
A UI Callback XML file defines the "questions" AM may ask to user during a authentication process. For better mapping to web interface, AM pre-define several types of callback for your use, such as Text-like, Password-like, or HTTPResponse-like callback. You can find the UI Callback XML files for the built-in authentication module at (assume you install AM on SJSWS7)
Check out LDAP.xml, AD.xml, and JDBC.xml for reference.
UI Callback Template
A UI Callback Template is a JSP file. Typically it contains HTML tag and AM UI specific tag. It is referred and used by the Callbacks element in UI Callback XML file to render the Callback XML to actual HTML page. You can find the UI Callback Template files at
Each <Callbacks> element in Callback XML has an attribute "template" indicating the template JSP for this <Callbacks>. But you may hardly to find a <Callbacks> with template value from all built-in Callback XML files. In fact, AM will use "Login.jsp" as the template JSP for this case. Out of box, the Login.jsp is used by almost all modules for all login page. So be careful of the side effect results from a hacked Login.jsp. My advice is to create your own template JSP.
Be aware that the template JSP is just a template used by AM authentication service. The service utilize JATO framework (a Sun's own MVC framework, like struts), so don't expect you can access the Login.jsp by put its name in URL. It just won't work !!!
OK, let's take a look at the Login Flow Chart step by step :
- User bring up browser and access http://yourhost.yourdomain.com/amserver/UI/Login (or via a URL link, or redirect by Policy Agent). Then AM check the domain name and parameters in this request to determine which Authentication Chain should be used. Assume the chain was found and its first login module is "XModule" (maybe somebody else made it), then a instance (Left block in the diagram) of XModule class is created by AM, its init( ) get called for initialization work. After this, AM check XModule's UI Callback XML file "XModule.xml", pick the first <Callbacks> element -- Callbacks Order 1。
- Access Manager parse Callbacks Order 1 (2 callbacks and no template assigned). Then it use default Login.jsp as HTML template to render the UI page. The outcome page will contain two HTML input fields. One is for NameCallback; the other is for PasswordCallback. AM also assign IDTokenN as the filed identifier to them. N is the position in <Callbacks> element. For NameCallback and PasswordCallback, their <Prompt> sub-element value will also be shown as leading label text.
- User then see the login page with two input fileds, key in the ID & Password.
- Then he click "Submit" button, an HTTP POST send the credential (in IDToken1=theid&IDToken2=thepass in body) back to /amserver/UI/Login servlet.
- Now it is the show time for module class. Its process( ) method is called with two arguments : a Callback[ ] array containing what user just key in and a Callbacks order number, currently should be 1 telling process( ) to validate credential for <Callbacks order="1"..>. At this point, the control is in XModule program. It should now verify the credentials and return a order number for next <Callbacks> if new question need to ask to user. For most case, the ID & Password pair is sufficient to determine the authenticaiton result and module class can simply return -1 for valid credentials to finish the module. The built-in LDAP module does so, that's why we usually see only one page before LDAP login. However, sometimes you get another page asking you to Change Password because your current password is going to expired. Under the hood, LDAP Server tells LDAP module the password is expiring in xxx days and the module process( ) return 2 to ask for password change. Let's assume our XModule need to ask more and return 2.
- Then AM internal found that process( ) returns 2, then check the Callback Order 2 XML-- one PasswordCallback, no template.
- AM then use Login.jsp as HTML tempate again, render a HTML page with only one input box for password (IDToken1).
- User see the 2nd page, then type in the password as requested.
- Then he click "Submit" button again. an HTTP POST send the credential back to /amserver/UI/Login again. The same XModule instance's process( ) get called again, but with new callback[ ] and State argument values. This time the State =2. You surely know why.
- The process( ) run different logic according to different State value (You should code it so). But the basic rule doesn't change:
> return -1 if the credentail is valid and no more question for user OR
> throws AuthLoginException if you're sure the login attempt is failed OR
> return another order number if you need to ask more
To complete my long diagram, we assume the picky XModule still need to ask more, and return 3.
- AM get 3 as return, similar to step 7, callback to user, and then get back... the interaction keep going.
- Finally, during processing Callback Order 4, module class can determine it is a successful login (Thanks God), return -1.
Above is a perfect scenario, but what if an error occurs in module class and you need to terminate the process immediately? You just need to throws a AuthLoginException anywhere anytime in process( ) method. When AM caught such exception, the follow-up order will not be executed and usually user get authentication failed or server internal error message. Depends on the severity of the error and module Condition, sometimes AM will forward user to next module.