Multi-Factor Interface Changes

Credentials can only be obtained from the Browser at the Presentation Layer where CAS code has access to the HttpRequest object, including Certificates, Cookies, and Forms data. In the currently distributed CAS server, the Presentation Layer is implemented using Spring WebFlow, but someone who prefers a different presentation Framework (Struts, JSF) could in theory replace this implementation. Before Multifactor Authentication this did not make much sense because there was only one Form for userid/password and so any complexity involved non-interactive alternatives and options like Gateway and Warn.

The connection between the Presentation Layer and the Business Logic of CAS is the CentralAuthenticationService interface class. Currently this interface contains methods that are adequate for Single Factor Authentication because they essentially stop and generate a Login TGT and a Service Ticket when the first Credential successfully authenticates. For Multifactor authentication, this interface needs to be extended with additional methods.

In the current code, a reference to the Bean implementing the CentralAuthenticationService interface is injected by Spring into every Bean that participates in the Web Flow. If you replace Spring WebFlow with a different framework, you need to acquire this reference somewhere else (JNDI, EL, ...).

The main difference is that while the first Credential to successfully authenticate creates a Login TGT object (with an associated Authentication object and a Principal name), this first Credential may not be sufficient to satisfy the requirements associated with the Service string to generate a Service Ticket.

The Presentation Layer either has a TGT ID from a Cookie or else gets a TGT ID returned the first time Credentials authenticate. It also has the Service string from the Request. The interface needs two things. First, it needs to be able to ask the Business Logic if the current TGT ID is adequate to issue a Service Ticket for this Service string, and if not to get back a list of Credential Type strings that indicate a set of Credentials that the TGT does not currently possess but would be useful in establishing the required authority to get the Service Ticket. This is not a list of required credentials, because some services might be willing to accept any one of three alternative credentials. Then all three would appear on the useful list, but the ST could be issued as soon as any one of the three was obtained.

The other extension to the interface takes an existing TGT ID and a new Credential (or group of Credentials), authenticates the Credentials, and adds them to the TGT along with any preexisting Credentials already in the TGT. This method is complicated by the return value or exceptions which have to indicate a number of possible errors:

  1. One or more Credentials in the list may fail to validate, and they must be distinguished from Credentials that did authenticate and were added to the TGT. In may cases this would represent a "password" supplied by a card or dongle device that was typed in wrong by the user and has to be reentered. We do not want the user to reenter "passwords" that did authenticate and are OK.
  2. One or more Credentials may have authenticated but resolved to an incompatible Principal name. If they are inconsistent with the Principal already stored in the TGT then the additional credentials cannot be added to the existing login and the user must either obtain different credentials or logoff the old Principal and establish a new identity. In an initial Login, however, inconsistent Credentials produce uncertainty and the user cannot be logged on until one or the other group of credentials is withdrawn and only a consistent set is presented.
  3. All of the Credentials presented may be OK, but they may still not be enough to generate a ST for the indicated Service string. More Credentials are required from a list of useful Credentials (this duplicates the query function described above but may merge it into the response from an additional authentication).

The interface changes can be simplified by presenting Credentials one at a time. Then the return or Exception generated by the call applies to that specific Credential. A group of Credentials can be presented one at a time and the Presentation Layer could decide what to do based on the response from each call. If you permit a Collection<Credential> to be passed, then you need some kind of Collection<AuthenticationResponse> to be the return.

The first step in the Presentation Layer is now to examine the Cookies for a TGT ID. If one exists then an attempt is made to issue the Service Ticket. This can fail if the TGT ID is invalid or expired, in which case the Cookie must be discarded and the Presentation Layer goes back to acquire Credentials. This logic has to be generalized to allow for a valid TGT ID that would have some valid Credentials, but not enough of the right type to issue the ST.

Currently, the WebFlow proceeds through the non-interactive Credentials first (X509, SPNEGO) and then the interactive Credentials. However, the current flow logic assumes that the process stops when the first successful authentication occurs. This changes when some authentications have previously succeeded but more are needed.

Because non-interactive Credential processing is not part of the distributed sample WebFlow, but must be added by the CAS administrator because every non-interactive Credential is an optional component, it is not a good idea to assume that non-interactive credentials are always tried or have already been tried if there is an existing valid TGT ID. SPNEGO has undesirable side effects in some configurations and may be enabled only when appropriate. X509 Certificates can be stored on a dongle that may be plugged in just before accessing the specially protected Service. Exactly how non-interactive Credentials interact will require some experimentation and customization and may represent WebFlow XML that you cut and paste from an example rather than something you just enable or disable by configuration.

The original design of the AuthenticationManager was to present a single Credential to each available AuthenticationHandler.supports() method which returns true if the Credential type is supported by that Handler. Currently Handlers make this decision based on the type of object implementing the Credentials interface. The Userid and Password are stored in an object of type UsernamePasswordCredentials and are supported by many different handlers using JAAS, JDBC, LDAP, Radius, other other backend mechanisms to validate passwords.

When you add Multifactor, you end up with a number of Password-like Credential types. These "passwords" are displayed on a card in your wallet, a dongle on your keychain, or an App on your phone. These passwords are then validated against a Web Service or a Network Appliance using one of the existing network protocols. This means that there will be additional types of Passwords (in most cases unaccompanied by Netid) that can be authenticated through existing AuthenticationHandler classes provided that these classes are configured with the network address of a Network Appliance instead of the traditional Netid store using K5 or AD.

Until we test deployment with a number of different widely used factors, it will not be possible to predict what the simplest adaptation of existing architecture will be. Perhaps existing AuthenticationHandlers can be adapted with minor changes.

Imagine that (for the sake of argument) we create a new "class PasswordLikeCredentials implements Credentials" as the common ancestor of all the one time use or time varying smart card passcodes. At that point we have two choices. The Java programming solution is to create a subclass for Verisign, Vasco, and every other App or device. Then the ApplicationHandlers could continue to make selection choices based on Credential type, but then we have to figure out how to configure type into Spring. This probably means creating property fields injected with values like "VerisignPasswordLikeCredentials.class".

However, none of these derived classes has any different fields or methods. They exist only so that one can make distinctions based on "instanaceof" tests. So an alternative is to not go overboard with subclassing and just add to the PasswordLikeCredentials class a String vendorType property . Then the AuthenticationHandler can be configured with a matching vendorType property and return true to AuthenticationHandler.supported() only if the vendorType property string in the Credential matches the vendorType property Spring-configured to the Handler Bean. Different instances of the same Handler Bean could accept different vendorType values and authenticate them to differently configured backend network server addresses.

Using Credentials Java type or String type names ends up with the same logic implemented slightly differently. However, a CAS administrator can add a new vendorType for a new vendor that uses an existing supported protocol just by changing the Spring XML, while if actual Java types are used then any new vendor can only be supported by writing and compiling a new Java class. Admittedly it is a trivial bit of Java programming, but it is real code that would be unnecessary if you do the type matching by using Strings instead.