A Java SE application or a Tomcat server runs under a native operating system userid. When it uses files and other local resources, it does so under the authority of that local system login.
However, when an SE application accesses a service on a remote system, the local userid doesn't necessarily propagate through the network. Some Java APIs expose their own custom mechanism to provide a userid and password (in the JDBC connection or in a Web URL). JAAS provides a common service that can be used by other Java network services.
A J2EE container may want to identify a remote user and associate this identity with a Request, the thread processing the Request, and perhaps a Session object shared across requests. JAAS provides a common API and some standard services that can support container based authentication, but it is up to the container to save the results generated by the JAAS authentication in its own management objects.
In the standard Java Runtime from Sun, there are JAAS plug-in LoginModule classes for Kerberos 5, LDAP, and several legacy network protocols (NT Lan Manager, NIS) that are probably no longer of current interest. However, given the standard interface exposed by JAAS, you can obtain software from third party vendors that either provide better versions of these protocols or new protocols for servers or network appliances.
To use JAAS, Java code creates a LoginContext associated with a bundle of configuration parameters that names one or more plug-in service providers. Each provider tries to validate the credentials against a network service. Success generates Subject and Principal objects chained to the LoginContext.
- The LdapLoginModule accepts a userid/password, uses them to login to an LDAP directory, and may return information from the LDAP User object in the generated Subject and Principal objects. LDAP attributes on the User object could be converted into Roles to make access control decisions, but in many cases this will require a customized version of the LoginModule that knows what attributes to fetch and how to generate the Roles.
- The Krb5LoginModule accepts a userid/password or a keytab file containing the secret key. It returns Subject and Principal objects containing the secret key and the Ticket Granting Ticket. The TGT can be used to gain access to other network services that use Kerberos.
- The Krb5LoginModule can alternately accept a Service Ticket and return the userid of the remote user who sent it in the Subject/Principal.
- The CasLoginModule (yes one has been written for JBoss) accepts the CAS Service Ticket, validates it to the CAS server, and returns the netid and Proxy info in the Subject/Principal objects. Through Proxy services, this identity can then be propagated to another JBoss server when a request is made to a remote JBoss EJB.
Because JAAS exposes a single published service provider interface, vendors of specialized identity hardware can write their own plug-in XxxLoginModule class and distribute smart cards, USB devices, or other forms of credential. Exotic credentials (more than userid and password) may require some customization in the user login interface of the application, but JAAS provides a standard language-wide interface.
Shared Authentication
CAS is a Single Sign-On mechanism. The user logs on once to the CAS server and identifies himself though that one server to all the applications on the network. However, CAS can only be used by Web based applications that can be adapted to use its protocol. When you cannot use SSO, then you still want to avoid ending up with a dozen different systems each with its own set of userids and passwords.
The next best alternative is a network based system of shared authentication. You select a reliable, secure system to hold the userid/password data. It must expose a network API that allows the password validation to be done by lots of different systems and languages. Then each system may provide a login form where the user has to enter a userid and password, but the validation is done centrally.
Back in the old days it was popular to validate passwords using the Novell Netware systems, or the Microsoft LAN Manager, or Unix NIS. However, modern systems tend to choose either Kerberos 5 or LDAP. Both of these protocols are supported by Active Directory, and most systems and languages have client support for one or both of them. Kerberos is inherently secure, while LDAP can be made secure if it runs over SSL.
Rather than coding specific Kerberos or LDAP authentication logic for every application, Unix systems developed the idea of a Pluggable Authentication Module (PAM). There is a single standard programming interface that every application can use. Behind that interface, a system administrator can plug in one or more service provider modules that use Kerberos, LDAP, a database system, or some network appliance to validate the userid and password and maybe return information about the user.
Shared authentication becomes more complicated as authentication itself becomes more involved. In the old days you just used passwords and that was good enough. In the new world it may be necessary to establish identity by using passwords and some physical device that plugs into a USB port. Pluggable authentication has to be able to extend to these more complicated environments.
JAAS - Pluggable Java Authentication
JAAS became the Java SE version of the PAM idea. Standard Java releases since 1.4 have included the javax.security.auth.* packages. They work like JDBC in exposing a single standard front set of classes that are extended by adding external libraries to the classpath that provide additional classes implementing a service provider interface. JDBC is extended by libraries of database drivers, while JAAS is extended by LoginModule classes.
The application program or container presents the name of a bundle of parameters. JAAS looks up the name in its configuration files, and the named bundle specifies the fully qualified name of one or more classes implementing the LoginModule Service Provider Interface. The application program or container provides credentials and JAAS passes them on to the configured list of LoginModules. Obviously the named class must be found somewhere in the Classpath, but whether it was added to the JRE, provided by the container, or bundled in the application is a local decision.
The first time someone learns about JAAS, they probably use it to validate a userid and password against a standard network server (like Microsoft AD). At first, the API seems more complicated than you expect:
- The Java code creates a Callback object that holds the userid and password. This object will be passed to JAAS, which will call it in the middle of login to obtain the userid and password parameters.
- Then the program creates a LoginContext object passing a character string naming an external JAAS configuration parameter bundle in one of the login configuration files and the Callback object.
- The login() method of the LoginContext authenticates the userid and password using whatever service provider class was specified and parameterized by the configuration bundle.
The reason for the callback class is that userid and password are not the only credentials someone can use to prove their identity. In addition to password, a Kerberos 5 KDC can authenticate using a keytab file containing the secret private key derived from the password. Keytab files are a common way to identify services residing on machines in the network. When the private key is stored on a USB device, then the callback provides an opportunity for a special Java driver to use the device to prove identity.
JAAS is different from most of the standard Java services in that it is not designed to work exactly the same on all systems. The "write once run anywhere" contract applies to Applications.The whole idea of JAAS was to remote protocol coding and local server configuration from the Application. What is left is just the request interface which is portable across all systems because it does almost nothing itself. The Application does not depend on whatever happens behind the interface. While the Sun class that does LDAP authentication is available on all systems, a vendor supplied plug-in that uses a network appliance could be unique to a small set of customer institutions, and an experimental authentication system may exist in only one place. So JAAS plug-ins are not expected to be 100% pure Java and completely system independent.
There are also some extra features to JAAS, particularly when it is used in Kerberos 5. However, before these features can be understood, we need to probe a bit more into the parts of the JAAS API that people do not learn when they are just using it to verify a password.
JAAS Login Configuration
A JDBC database driver requires no special configuration, but then the client application program has to supply the URL to the server and the connection parameters. JAAS is designed to remove the entire authentication process from the application and move it to the system administrator. As a result, the JAAS "login configuration" looks something like the configuration of a Spring Bean or a Servlet Filter.
The starting point of JAAS configuration is a bundle of parameters that specifies the fully qualified name of a plug-in class that implements the JAAS Service Provider Interface (SPI). That is followed by a optional set of property values that configure the instance of that class object.
myauth { com.sun.security.auth.module.LdapLoginModule REQUIRED userProvider="ldaps://yu.yale.edu/dc=yu,dc=yale,dc=edu" userFilter="(&(sAMAccountName=\{USERNAME\}))" useSSL=true; }
This configuration bundle selects the standard Sun LDAP plug-in module supplied with every JRE. It provides a URL with server name and starting search location, and an LDAP query that can be used to locate an LDAP User object given a Netid argument (represented by the {USERNAME} variable.) With this configuration, JAAS will go to a Domain Controller in the Yale Active Directory, search for a legacy userid matching the userid supplied by the JAAS client application, and then validate the password against the unique object returned from the search. Using a search gets around the problem when some departments create userids in their own OU instead of using the common Users container. The bundle has been given the name "myauth". A client program can pass this name to JAAS to select this particular configuration and authenticate using the Yale AD.
This particular file format is used by the default JAAS services provided by J2SE. However, under the covers the com.sun.security.auth.module.LdapLoginModule is just a Java class that implements the JAAS SPI interface, and the interface specifies how the named parameters should be set in an instance of the class. The standard JAAS implementation can be hijacked by a particular container environment (Spring, JBoss, ...) that exposes the same API to the client programs and uses the same plug-in modules, but uses a different configuration file and maybe adds some additional processing in the middle. So be prepared to see a version of this same configuration bundle expressed in XML, and possibly in different XML formats in different systems.
One or more of these named bundles of classname+parameters can be added to a JAAS "login configuration file". There are three places you can find configuration files:
One or more configuration files using the format in the example above can be specified in the Java Runtime itself. Specifically, the lib/java.security file has a section :
# # Default login configuration file # #login.config.url.1=file:${user.home}/.java.login.config login.config.url.1=file:${java.home}/lib/security/jaas.conf
In this example, a login configuration file named "jaas.conf" located in the JRE lib/security directory has been added to the standard JVM environment and will be available to any program that uses this JRE.
The container that you use may provide its own JAAS configuration, sometimes in its own format. In JBoss, for example, the default server configuration contains a JAAS configuration in a JBoss XML format in the server/default/conf/login-config.xml file. Expressing the previous bundle of parameters in JBoss syntax would look like this:
<application-policy name="myauth"> <authentication> <login-module code="com.sun.security.auth.module.LdapLoginModule" flag="required"> <module-option name="userProvider">ldaps://yu.yale.edu/dc=yu,dc=yale,dc=edu</module-option> <module-option name="userFilter">(&(sAMAccountName=\{USERNAME\}))</module-option> <module-option name="useSSL">true</module-option> </login-module> </authentication> </application-policy>
The third possibility is that the individual application may have a JAAS configuration file embedded in it. The path to the login configuration file is a Java system environment variable that can be specified by the application before it calls JAAS.
Now for the important point. Login configuration files are merged, they don't replace each other. Furthermore, there is no rule that allows a named bundle of parameters to be overridden by another source. The name the application or container is looking for ("myauth" in these examples) must be found in only one configuration file or JAAS will simply throw an exception.
What's in a Name?
JAAS configuration offers enormous flexibility, but this leaves an ambiguity that should be resolved by institutional policy. If it is not given some global thought, you leave room for inconsistency and confusion.
During application development, it makes sense for system administrators to create a set of pre-packaged JAAS configuration parameters representing the different supported types of authentication and different back end servers that the local environment supports. This suggests names like MITKerberosAuth or ActiveDirectoryLdapAuth. The application programmer can regard these names as configured system services and use them to get started.
However, when an application is deployed in production, the JAAS design seeks to completely separate the authentication mechanism from the application code. Even using a name that indicates a particular protocol or back end service contradicts this design objective. This produces two plausible naming conventions.
An application could use its own name as the JAAS configuration bundle name. Thus the CAS Server could specify "cas-server" as the bundle name. This is a good default for a product that is distributed to lots of different institutions that have to make up their own local policy about what authentication mechanism to use, but if the default is not changed then the typical institutional JAAS login configuration file will have dozens of identical configuration bundles differing only in the the application name at the start of the bundle.
Alternately, an institution could specify a small number of bundle names representing the generic type of authentication (an authentication design pattern) that an application might select (SingleSignOn, SingleSignOnWithProxy, SharedAuthentication). Note that these names do not reflect a particular protocol or server, but neither are they limited to a particular application. Unfortunately, these design patterns have not be assigned industry standard names, so this preferred approach will probably vary from institution to institution.
The JAAS Objects
An application using JAAS creates a LoginContext object associated with the name of a bundle of configuration data. Up to this point each example has shown the configuration of a single fully qualified classname and its parameters. However, JAAS allows a named bundle to configure more than one plug-in module, allowing the user to make a single call and log into either the first system that accepts him or more than one back end system (Kerberos and LDAP for example).
The JAAS client application calls the login() method of the LoginContext object. After a sucessful login, the LoginContext has acquired a javax.security.auth.Subject object and one or more javax.security.auth.Principal objects (there will be one Principal for every sucessful login to a different plug-in class in the named configuration bundle).
If you are just using JAAS to validate a userid and password, you will never inspect the Subject or Principal objects. Immediately after the sucessful login(), such code calls the logout() method of the LoginContext to delete all the generated objects and clean everything up.
Actually examining the generated objects may provide a bit of interesting but probably useless information. For example, after a successful LDAP login to Active Directory by userid "gilbert" (which in the previous configuration example above searches for an LDAP User object with sAMAccountName=gilbert), one might be mildly interested that the Distinguished Name of the LDAP User object was "CN=gilbert, CN=Users, DC=yu, DC=yale, DC=edu", but this is hardly an unexpected result.
The JAAS Objects when you use Krb5LoginModule
JAAS internal behavior becomes a lot more interesting in the special case where you use the standard Sun Kerberos 5 plug-in module. Extra function becomes available and a variety of new use cases is exposed when you add a few extra configuration parameter options.
The first interesting parameter is "useTicketCache=true". This exploits the special permission that JAAS has to do different things on different operating systems. With this parameter, JAAS shares whatever is the native host operating system Kerberos login ticket storage mechanism. On a Windows machine logged into an Active Directory domain, JAAS can simply connect to the existing login and create a Subject and Principal that reflects the host environment native Windows userid.
SPN
Up to this point, we have discussed the userid of individuals. However, in Kerberos each server has an identity called the Service Principal Name. A Web Server on a machine named "secure.its.yale.edu" has an SPN of "HTTP/secure.its.yale.edu". When you use real Kerberos protocol, including real K5 Service Tickets, then the client logs on to Kerberos with his userid, the server logs on with the SPN, and then the client obtains a Service Ticket that the server can validate.
JAAS can be used to log the service into Kerberos using the SPN. In theory, you can use the standard JAAS login simply replacing the userid with an SPN, but while a service has an identity it does not typically use a regular password. The more common convention is for a system administrator to use Kerberos utility tools to create a "keytab" file. The keytab file contains the private key that Kerberos really uses to identify services on the network. The Krb5LoginModule has parameters "useKeyTab=true" and "keyTab=path/to/file" to support the login of a service.
In the special case where Active Directory provides the Kerberos service, Microsoft allows a standard SPN to be "mapped" to a dummy User object also in AD. For example, one can create a dummy user named "s_cas" in Active Directory and give it a normal password. Then, one can use administrator tools in AD to map the "HTTP/secure.its.yale.edu" SPN ID to the "s_cas" userid. Now instead of using the keytab=... form of login, the service (corresponding to URL "https://secure.its.yale.edu/cas") can login using the standard userid/password API of JAAS with the userid "s_cas" and the password assigned to that dummy user account.
Either way, after the service logs in through the Krb5LoginModule of JAAS, if it has requested that the ticket be stored in a ticket cache and uses the "storeKey=true" configuration parameter, the Subject and Principal objects created by the LoginContext contain the information that the Java service needs to validate real Kerberos 5 Service Tickets.
Real Service Ticket Validation
Which brings us to yet another use of the Krb5LoginModule. In addition to logging real desktop user clients into Kerberos with a userid and password, and logging a service SPN into Kerberos using a keytab or mapped userid, Krb5LoginModule can "login" a the remote network client using the Service Ticket supplied by that remote client specifically for use by this particular service SPN.
Obviously somewhere under the covers there is an explicit description of this specific use of the JAAS API. However, in most common cases it is used under the covers by some other technology or library. The first step is to log in the service (using the keytab or mapped service userid). Then from the credentials stored in the Subject and Principal objects the code can extract the private key of the service which is used by Service Ticket validation.
Now you need an entirely different LoginContext (because now the Subject will be the remote use of the service and not the service itself). It too has to be configured to use Krb5LoginModule and obviously it will be using the same KDC as the previous step. However, it will have different configuration parameters and therefore will probably use a different bundle name. Now we waive our hands a bit because the details are uncertain, but somehow the remote user obtains a Service Ticket targeted to this SPN and it transmits the ticket to us. Using the Ticket instead of a userid/password as credentials, and plugging into the second LoginContext the private key obtained from the Subject object of the first LoginContext we provide Krb5LoginModule everything it needs to validate the Service Ticket and create in the second LoginContext Subject and Principal objects that represent the authenticated remote user.
Now the gotcha. There is no public standard Java API that directly obtains Service Tickets for a remote service. However, GSSAPI does this under the covers as part of its protocol. When it does this, GSSAPI and JAAS operate together sharing Kerberos configuration info and some ticket and key objects. It is through this collaboration and not through some pure, naked Kerberos API that we most commonly find Kerberos Service Ticket validation operating. In Web applications, this means the use of SPNEGO authentication.
JAAS in a Container (JBoss)
The JAAS client program API and the Service Provider Interface (SPI) to the plug-in modules represent two perfectly acceptable interfaces that anyone trying to solve the same problem would have to reinvent. Furthermore, the particular Sun plug in modules provide sophisticated services (especially Krb5LoginModule) that it would be wasteful to try and reproduce.
However, the standard JAAS service is a J2SE interface. The application creates a LoginContext, logs in, and obtains Subject and Principal objects. Only the application knows it has done this. Furthermore, the LoginContext and Subject are private to the class that does the login and have no wider visibility unless the application code chooses to invent some convention.
JBoss exposes to its JVM environment what might be called JAAS+. It is the standard JAAS service, plug-ins, and an XML version of the standard configuration file. The extra added feature is that the JAAS generated Subject and Principal objects are not only returned to the caller's LoginContext, but they also become part of the container's Security Context environment. They become a feature of the Request and the Thread that has been assigned to process it. When an EJB is called, they propogate to the EJB environment, even across a Remote call to an EJB on another JBoss server.
For this to work, the Subject/Principal has to be in some sense "serializable". Obviously if you login to Krb5LoginModule the ticket you receive cannot itself be transmitted to another machine. If the login presented a userid/password, then these "primary credentials" can be stored in the Subject/Principal, serialized, transmitted to a second system, and then the second system can do a second primary login presenting the same userid/password to its own Krb5LoginModule.
It remains to be seen how the SPNEGO support now incubating in JBoss will accomplish the same remote propagation of credentials when JBoss never had primary credentials to transmit.
Who's on First?
The CAS Server is (or can be) configured to use JAAS to validate a userid/password typed into the Login form. At Yale, JAAS has been configured to authenticate against the MIT K5 KDC.
However, other applications regard CAS itself as an authentication mechanism. If these applications (like JBoss) expect to authenticate users through a JAAS call, then it is possible to supply them with a CAS plug-in that uses the JAAS Service Provider Interface (SPI) to validate credentials (the ticket id) against a configured back end system (the CAS Server).
The first step toward sanity is not to confuse these two distinct uses of JAAS by the CAS Server (against Kerberos) and by the CAS client (against the CAS Server itself).
The simplest version of a CAS JAAS plug-in module simply validates a service ticket. Imagine that the code has been written as a class named CasServiceLoginModule. Configure the fully qualified class name under some bundle name in the JAAS configuration files. We assume the plug-in takes two parameters, the URL of the /serviceValidate operation in the CAS server and the value that will passed to CAS as the service= parameter. The ticket= value will be presented to the module as a parameter. If the environment strongly expects a userid/password, assume the ticket id will be passed in the password field. The CasServiceLoginModule then connects to the ServiceValidate URL, authenticates the ticket, obtains the userid and any other information that CAS chooses to share, and stores this in the Subject and Principal objects generated.
A CASify Lombardi - Teamworks Application via Jaas exists for JBoss. It has the additional function of obtaining a Proxy Granting Ticket so the Subject and Principal objects in the JBoss security context can "serialize" and propagate to a second JBoss system when there is a remote EJB call. The first JBoss authenticates the ticket as described previously, but it obtains a Proxy Granting Ticket and stores it in the Subject/Principal structure. Then when the JBoss Security Context asks the Subject to serialize itself (which only happens when you are making a remote EJB call), it obtains a Proxy Service Ticket for the remote system and sends its ID as content of the serialized Subject/Principal. On the receiving system, when these objects are de-serialized the code validates the Proxy Service Ticket and inserts the Netid in the secondary Subject/Principal.
There are a number of problems that make this code difficult to generalize. For one thing, the Proxy Callback requires that code be inserted into the Servlet layer of the container, and then the Ticket Validation and the Callback routine have to synchronize what they are doing. This is probably a system dependent solution. More importantly, you can only get a Proxy ticket if you know the service= of the receiving system, and that implies understanding the current system specific Security context and request processing context.Again, it will be interesting to see how SPNEGO handles this same problem if it gets integrated into JBoss, but that would not solve the problem for another container. Strictly speaking, the uncertainties here are not in the JAAS function but rather some missing features of the javax.security.auth.Subject interface.