Technical Overview

New CAS documentation site

CAS documentation has moved over to apereo.github.io/cas, starting with CAS version 4.x. The wiki will no longer be maintained. For the most recent version of the documentation, please refer to the aforementioned link.

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 Authentication 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 or 6.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.

The CAS Mechanism

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(s)://cas_server/cas/login?service=http(s)://other_server/application1

When the CAS server receives the request, CAS authenticates the user either via prompting the user to login or by checking to see if they already have an active SSO session with CAS (Ticket Granting Ticket, TGT). If authentication is successful, CAS verifies that application1 is an allowed application by checking its persistent store of CAS registered services. If also successful, CAS generates a service ticket (ST) and programmatically forms a new URL request (redirection) back to application1 with the ST as a request parameter in the query string. Remember that the user does not see any of this. Also, please note that HTTPS is marked with parentheses. It is critical that you understand how HTTPS can be used. CAS will make no assumptions outside of the authentication security mechanism. HTTPS recommendations and practices will be discussed in separate documentation.

http(s)://other_server/application1?ticket=ST-8670-123buTvFFjo980

The other server receives this request through a CAS client (either a servlet filter or a module) 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.

CAS receives and validates this secure server-to-server request, then fulfills the application server's HTTPS CAS client request and returns an XML message of "success" along with the authenticated username. Remember that all of this is occurring whilst the user has only made a single request to access application1. The original request then exits the CAS filter/module and allows 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.

GenerateServiceTicketAction.java (CAS Server)
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.

DynamicRedirectViewSelector.java (CAS 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.

CASFilter.java (CAS Client)
...
    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.

ServiceTicketValidator.java (CAS Client)
...
  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.

Ticket Registry

Proxy Chaining

Single Sign On (SSO)

Shared Components

Common Authentication

Conclusion