JAAS Implementation of Single Sign-On for Multiple Web Apps
Now let's get to the implementation of single sign-on based on JAAS for Web applications deployed on the same server. To make it easier for discussion, I use an LDAP-based login module as an example.
Login Configuration for Single Sign-On
When multiple Web applications are deployed on a single application server instance, usually each Web application authenticates users based on its own data store. One Web application may use a relational database to store user security information and another may use an LDAP server to authenticate users. The JAAS PAM architecture allows an enterprise application to define a stack of login modules, each of which is independent and communicates to its own data source. To achieve single sign-on, each Web application defines its own login module and all modules are stacked in a certain order in the login configuration file.
JAAS defines a configuration interface, which a user can implement to store configuration data in a file or a database. In this discussion, I assume a file-based login configuration and I use a Logistics enterprise application as an example. The Logistics enterprise application contains two Web applications: carrier Web application and shipper Web application. Carrier users log into the carrier Web application to report location and movement events for shipment and Shipper users log into the shipper Web application to query the delivery events of shipment.
If a 3PL (third-part logistics) uses this Logistics enterprise application, employees of the 3PL will need to log into both Web applications to maintain and query shipment status on behalf of both the carrier and shipper. To achieve single sign-on in this situation based on JAAS, the login configuration file needs to be defined as follows:
required name=ShipperSecurityDomain useSharedState=true;
Notice two LDAP login modules are defined: one for the carrier Web application and one for the shipper Web application. These two login modules may communicate to the same LDAP server or different ones, depending on the configuration (using name attribute to link to the particular configuration block) for the LDAP server. For single sign-on, the flags for both login modules are specified as required (other flags include optional, sufficient, and requisite), which means the user must be successfully authenticated by both LDAP servers.
Additionally, the attribute useSharedState is specified. If it is true, this LoginModule retrieves the username and password from the module's shared state, using "javax.security.auth.login.name" and "javax.security.auth.login.password" as the respective keys. The retrieved values are used for authentication. The username and password are set into shared state by LoginContext before invoking the login method of the login module instance. The useSharedState attribute allows all login modules to share the user's credential information, which is captured only once.
An XML-based Configuration for Login Module
When the login module communicates with the data store, be it a database or an LDAP server, it requires certain configurations for common or special attributes. Using the LDAP login module for example, it will at least know the hostname of the LDAP server. Depending on your implementation, you may need different configuration parameters. Listing 1 shows a sample configuration block for the two LDAP login modules defined in the previous sections.
This configuration needs to be loaded when the application server starts up so it can be shared by Web applications loaded later. I will not discuss each entry in detail but instead focus on the parts that are relevant to single sign-on.
While each block is being loaded, the initializer class is invoked. The initializer reads all the properties and registers them in the domain manager class with the name specified by the name attribute. The value of this name attribute is the same as the one specified in the login configuration file. This is how the login module entry is associated with its configuration block.
If the configuration is simple, you can even configure it when you define the login module in the configuration file, such as:
InitialCapacity="0" MaxCapacity="10" Properties="user=cymbio;password=cymbio
However, if your initialization for a login module requires substantial setup, a separate initialization block is the preferred approach.
In the two sample LDAP initialization blocks, the carrier security domain configures the login module to talk to the carrier.cysive.com LDAP server while the shipper login module communicates with shipper.cysive.com. You can easily substitute one of them with a relational database login module if user security information is stored in a database.