What is CAS Technically?
CAS is an Enterprise Java solution to web application authentication that also provides the benefit of Single Sign On (SSO). Technically, SSO can be achieved because the authentication can be removed from the web application and handled centrally. And, when this authentication is handled by a single service, access to many services can be granted once and "remembered" for the life of the web session or even longer though not recommended. Remember too that the SSO feature does not have to be employed and yet CAS authentication still provides a quality authentication mechanism.
CAS is a collaborative effort that has produced a very solid central authentication service using the Java development platform. CAS also provides a broad selection of clients for use with all of the Enterprise and scripted web technologies.
Runtime Environment
CAS is written to execute within a standard Java 1.5 Runtime Environment. Versions previous to 3.1 only required Java 1.4. As CAS aims to operate in a self-contained manner, there is no need to place any special configuration files into your Java Runtime Environment. The only exception to this is if you are using a Java Authenticaton and Authorization Service (JAAS) policy file.
CAS provides an example user interface that's designed to run in any container that supports the Servlet 2.4 specification. A good container is the Tomcat 5.x series, though any 2.4 servlet container will work. CAS will run on earlier versions of Tomcat e.g. 4.x if the older style JSTL tags are used. It would be easier to use the newer Tomcat version.
Tightly Coordinated Actions
The CAS authentication and service registry is a secure way to authenticate users and provide web application or web service access. Authentication credentials are managed by the CAS server and your application servers never touch this information. User access to your application is quite involved. A multi-request approach adds a layer of security and truly logical mechanism where authentication is isolated from the application.
Access to your application begins when the user calls for a CAS login including the application URL or service needed. This looks very much like this.
http://cas_server/cas/login?service=http://other_server/application1
When the CAS server receives the request, CAS programmatically forms a new URL request (redirection) and calls application1 and adds a unique one-time-only random ticket (String) as a request parameter. Remember that the user does not see any of this.
http://other_server/application1?ticket=ST-8670-123buTvFFjo980
The other server receives this request through a CAS client servlet filter that's been configured with application1. It parses the ticket and starts a new HTTPS connection with the CAS server. This new request is formed programmatically by the CAS filter Java code and it's commonly called the "service validation" step. It receives an SSL certificate after it sends a request to the CAS server. By protocol and Java standard, the certificate is compared by Java to it's collection of trusted certificates. If the certificate is "trusted", the HTTPS communication will occur.
CAS receives this secure request and prompts the user for his password. This is where the CAS server authenticates the user that's requesting access to application1. Remember that all this is occurring and the user has only made a single request to access application1. CAS will also verify that application1 has been loaded into it's persistent store of CAS registered services. It is, so CAS presents an HTML login screen to the user. The user enters his credentials and CAS will do the verification.
Once the user has been authenticated, the CAS server fulfills the HTTPS request by the application server and returns an XML message of "success" and the authenticated username included. The CAS filter can now allow application1 to serve content to this newly authenticated user.
A Source Code Perspective
For most of us, the best way to understand how something works is to review the source. And, with CAS we can because the source is provided openly to the public. This discussion will describe a CAS authentication and service access scenario between a CAS server and an application server using the Java code that's running on the two servers. The code module and server will be given for each referring listing or snippet. This also assumes you have the source code for the CAS Server 3.1.1 and the CAS Client 2.0.11 (Yale).
The user first makes a request to the CAS server for login and access to one of the CAS registered services.
http://cas_server/cas/login?service=http://other_server/application1
The CAS web server application uses Spring Web Flow and the URL handler ultimately sends the login request to GenerateServiceTicketAction. This is where the service ticket is placed in Request scope to be sent to the CAS client filter.
public final class GenerateServiceTicketAction extends AbstractAction { /** Instance of CentralAuthenticationService. */ @NotNull private CentralAuthenticationService centralAuthenticationService; protected Event doExecute(final RequestContext context) { final Service service = WebUtils.getService(context); final String ticketGrantingTicket = WebUtils.getTicketGrantingTicketId(context); try { final String serviceTicketId = this.centralAuthenticationService .grantServiceTicket(ticketGrantingTicket, service); WebUtils.putServiceTicketInRequestScope(context, serviceTicketId); return success(); } catch (final TicketException e) { if (isGatewayPresent(context)) { return result("gateway"); } } return error(); } ...
When the ticket has been placed in the request scope, CAS will now redirect the user request to the application using the service URL and the service ticket generated. The default in the switch-case block would handle the redirected request now to the application server.
public final class DynamicRedirectViewSelector implements ViewSelector { public ViewSelection makeRefreshSelection(final RequestContext context) { return makeEntrySelection(context); } public boolean isEntrySelectionRenderable(final RequestContext request) { return false; } public ViewSelection makeEntrySelection(final RequestContext request) { final WebApplicationService service = WebUtils.getService(request); final String ticket = WebUtils.getServiceTicketFromRequestScope(request); final Response serviceResponse = service.getResponse(ticket); switch (serviceResponse.getResponseType()) { case POST: final Map<String, Object> model = new HashMap<String, Object>(); model.put("parameters", serviceResponse.getAttributes()); model.put("originalUrl", service.getId()); return new ApplicationView("postResponseView", model); default: return new ExternalRedirect(service.getResponse(ticket).getUrl()); } } }
<STOP!> This is a critical timepoint. The CAS server has processed the user's request and the result of this redirection would be the expected application content. In the interim, before this request is processed, the application server using CAS client code will send an HTTPS request to the CAS server and have it authenticate the requesting user sharing no connection or resources with the original CAS user request. The CAS client servlet filter here sends the HTTPS request for user authentication. Ignore the proxy list for now, but notice the proxy validator gathers the service and the ticket. This method returns the user returned by the CAS server over HTTPS and within the CAS returning XML message.
... private String getAuthenticatedUser(HttpServletRequest request) throws ServletException { ProxyTicketValidator pv = null; try { pv = new ProxyTicketValidator(); pv.setCasValidateUrl(casValidate); pv.setServiceTicket(request.getParameter("ticket")); pv.setService(getService(request)); pv.setRenew(Boolean.valueOf(casRenew).booleanValue()); pv.validate(); if (!pv.isAuthenticationSuccesful()) throw new ServletException( "CAS authentication error: " + pv.getErrorCode() + ": " + pv.getErrorMessage()); if (pv.getProxyList().size() != 0) { // ticket was proxied if (casAuthorizedProxy == null) { throw new ServletException("this page does not accept proxied tickets"); } else { boolean authorized = false; String proxy = (String)pv.getProxyList().get(0); StringTokenizer casProxies = new StringTokenizer(casAuthorizedProxy); while (casProxies.hasMoreTokens()) { if (proxy.equals(casProxies.nextToken())) { authorized = true; break; } } if (!authorized) { throw new ServletException( "unauthorized top-level proxy: '" + pv.getProxyList().get(0) + "'"); } } } return pv.getUser(); } catch (SAXException ex) { String xmlResponse = ""; if (pv != null) xmlResponse = pv.getResponse(); throw new ServletException(ex + " " + xmlResponse); } catch (ParserConfigurationException ex) { throw new ServletException(ex); } catch (IOException ex) { throw new ServletException(ex); } } ...
The return pv.getUser() method comes from the class ServiceTicketValidator. ProxyTicketValidator (pv) extends ServiceTicketValidator. The validate() method will populate the user member if the CAS XML message comes back successfully. This method is where the separate HTTPS request is created to validate the questioning user.
... public void validate() throws IOException, SAXException, ParserConfigurationException { if (casValidateUrl == null || st == null) throw new IllegalStateException("must set validation URL and ticket"); clear(); attemptedAuthentication = true; StringBuffer sb = new StringBuffer(); sb.append(casValidateUrl); if (casValidateUrl.indexOf('?') == -1) sb.append('?'); else sb.append('&'); sb.append("service=" + service + "&ticket=" + st); if (proxyCallbackUrl != null) sb.append("&pgtUrl=" + proxyCallbackUrl); if (renew) sb.append("&renew=true"); String url = sb.toString(); String response = SecureURL.retrieve(url); this.entireResponse = response; // parse the response and set appropriate properties if (response != null) { XMLReader r = SAXParserFactory.newInstance().newSAXParser().getXMLReader(); r.setFeature("http://xml.org/sax/features/namespaces", false); r.setContentHandler(newHandler()); r.parse(new InputSource(new StringReader(response))); } } ...
The SecureURL.retrieve(url) method returns an XML response from the CAS server securely using HTTPS. These communications can be further secured by IPTables or software firewall instructions.