CAS 3.x can provide CAS 2 compatibility with a single Spring MVC SimpleFormController associated with a single URL (the LoginController class, but called the "Form Controller" here for emphasis).
When the URL is presented without form data, the controller checks for a TGT cookie. If one is present, the user is already logged on, a new ST is generated, and a View is designed to Redirect back to the Service with the ST. Otherwise the form is displayed.
When the URL is presented with form data, the Authentication Manager is called with the Userid and Password embedded in a Credentials object. If the userid or password do not validate, return to the View to present the form again with error messages. If they validate, then generate a TGT and ST, return, and chain to the View that Redirects back to the service.
Additional CAS 3.1 function doesn't require any meaningful changes to this code. However, two new options must be explored that have modest impact.
It should be possible to add buttons to the Form that, when pressed, route the request to alternate Views. One use is NTLM (the "use my current Windows logon" button) and one is Shibboleth (the "logon from another college" button). Both services are implemented as Filters which, to achieve selectivity, are mapped to specific URLs. Pressing either button logically redirects the Browser to the URL to which the Filter is mapped triggering extra processing that is outside of the scope of CAS. When the Filter is done, an enhanced (wrapped) HttpRequest object is presented to the Spring MVC Controller associated with these URLs. That "Post-Filter" Controller extracts Credentials from the HttpRequest object, calls the CAS layer with the credentials, and looks for a TGT and ST to be issued. If the credentials are insufficient to satisfy the requirements of the Service, then I would propose that the Post-Filter Controller exit back through the View that presents the Form with Error Messages, since we have to tell the user that he has to provide a local userid and password to use the service.
To allow non-interactive foms of credentials (X.509 Client Certificates for example), the logon URL to which CAS clients redirect will be a different URL than the one to which the logon Form submits data. The first URL will be associated with the "Pre-Form" or "Non-Interactive" Controller that processes all the non-interactive authentication methods. It calls a sequence of plug in components that generate Credentials of various forms from the HttpRequest object. Cookies (including the TGT session cookie) are extracted this way. So is the X.509 Client Certificate. So is the client IP address if it is to be considered. This collection of credentials is then presented to CAS. Each credential is passed in turn to each AuthHandler, which process what they understand and ignore what they don't. At the end of this pass, we have an Authentication with all of the Subject Entries implied by any non-interactive credential and all of the Attributes that could be obtained from them.
At this point, we look for an existing TGT. Typically we find the TGT with a cookie, but if an institution chooses to support cookieless-browsers we could find the TGT using information from a Client Certificate or even by matching the IP address (ick). The supplied standard code will only do the existing Cookie logic, but an institution can add plug-in logic here as an extension point.
Generally speaking, the non-interactive credentials don't change after a TGT is issued. Yes you could in theory login to CAS, add a Client Certificate to your Browser, and then go back to CAS a second time. In this highly unusual case, you would have new non-interactive credentials to merge into your TGT. The design should allow it. However, in normal processing if an existing TGT is found, then all the information gathered from the non-interactive AuthHandlers will duplicate stuff already associated with the TGT and at best this will update the Timestamps of, for example, Subject Entires whose authentication method was "from Client Certificate".
If a TGT is found then after a very weak effort at merger we fall through to previous logic of the Form Controller when a Cookie is present: take the new ST and (if the authentication is strong enough) exit through the View that the Form Controller used to Redirect back to the Service.
As a second quick sanity check, if there is no existing TGT (or a TGT whose Authentication is too weak for the Service) and none of the non-interactive AuthHandlers have generated any new meaningful Authentication contents because, for example, there was no Client Certificate, then again fall through to the previous logic exit to the View that displays the Form.
So what is left is the situation where there was no existing TGT, but there was a Client Certificate or some other meaningful non-interactive Authentication data generated from the pass through the AuthHandlers. Generate a new TGT. Use the data gathered up to this point as the Authentication backing the TGT. Create a Cookie. Try to get an ST for the Service.
However, the non-interactive authentication may not be sufficient to meet the strength requirements of the service. This we believe is a Controller decision because the only way that the Service can indicate what strength of authentication it requires is by adding something to the URL. So CAS exits back to the Controller with an object (the RequestContext) that contains the TGT, Authentication, ST, and accumulated Exception objects from errors detected by the AuthHandlers. The controller decides if the ST is strong enough. If it is, it exits to the View that redirects back to the Service with the ST. If not, then it exits to the View that writes the Form (with the slight enhancement that now the Form is written with a Cookie that refernces the weak TGT).
The Non-Interactive processing is entirely transparent to the Form Controller. It does not require changes to the existing LoginController class. Either the existing LoginController.showForm() method will never be called (and its logic will be moved to the Non-Interactive Controller, or upon further reflection we may find it convenient to have it called when it is time to perform its function.
An unresolved design point is the most efficient way to fill in attributes. If the non-interative AuthHandlers lookup attributes during their initial pass, then whenever there is already a TGT there will be some wasted time refetching attributes the TGT has already seen. If you don't defer looking for the TGT until AuthHandlers are called, you cannot support cookieless browsers. The more effient solution (which I think will be satisfactory for all non-interactive authentication methods) is to defer any serious fetch of attributes from a database or LDAP source until after the old TGT (if any) is located and then only fetch attributes for Subject Entries added to the TGT by this pass through the non-interactive logic.