Single Sign On to WebAdvisor Using CAS, ClearPass, and a Custom Java Filter

Empire State College Contribution

The Empire State College has graciously contributed the code and documentation below as free and open source solution that may be useful to other institutions. Please note that this solution may or may not work for others and it comes with no warranty or support.

Overview

As a proprietary product, WebAdvisor, which does not have built-in CAS authentication support, is not the easiest to CASify. However, there were some mitigating circumstances at Empire State College (ESC) that made this effort feasible:

  1. ESC already has implemented ClearPass
  2. WebAdvisor at ESC uses the same LDAP directory for authentication as CAS
  3. ESC has licensed Datatel's SSO API for portals
  4. WebAdvisor is a Java Web application

Design

The filter implemented for ESC leverages Jasig CAS Client for Java for all of its interactions with the CAS server. The filter also inherits some basic functionality from the base classes provided with the Jasig client.

Since this filter is configured to intercept every request coming to WebAdvisor, it is streamlined to introduce little or no overhead. Based on the URL pattern of the request, the filter looks for whether it should take any action or to let normal processing proceed to WebAdvisor itself. This decision making is summarized below:

  • If the request matches the WebAdvisor login request, redirect to the /Login sub-context
  • If the request matches the /Login sub-context, meaning that the CAS login was completed successfully, the filter performs its main function: login to WebAdvisor
  • If the request matches the WebAdvisor logout request, remove the CAS assertion from the HTTP session and let WebAdvisor continue
  • Otherwise, let the request through unmolested

The filter uses WebAdvisor SSO API that was originally designed to simplify login to WebAdvisor from a third-party portal. This API requires both the username and the password of the user, so the ClearPass implementation at ESC comes handy. The API works as follows:

  1. A Web application, in this case this custom filter, uses HTTP POST to deliver an SSO request in XML that contains the username and password of the user seeking access
  2. Upon successful authentication, WebAdvisor returns an SSO token

Datatel's documentation suggests that this SSO token can be added to a WebAdvisor URL and the user's browser can be redirected to that URL. Unicon's analysis of the security of this token revealed that if an adversary somehow obtained this token, say by monitoring an open network or by compromising browser's history, the token could be used to impersonate the user. Such bearer tokens can be secured by following means:

  • Make the token invalid right after successful authentication, so it usable only once
  • Make the token valid for a very short amount of time
  • Make the token valid only from the IP address to which it was legitimately issued

Unfortunately, WebAdvisor does not do any of the above.

In order to protect the WebAdvisor SSO token, this filter is designed to never reveal it to the outside world. It obtains the token, makes the authentication request on user's behalf, retrieves the WebAdvisor response, and replays that response to the user's browser. The token is not retained by the filter. It is logged in the filter's log, but only in DEBUG mode, which should not be used in production.

Building the Filter

The build process uses Apache Maven WAR Overlay method. This method is a convenient way of modifying an existing WAR file and adding to it. In this case, the Datatel's WAR file is modified by the build process by adding the custom code, dependent libraries, and configuration files. The only manual step involves additions required to the original WebAdvisor's deployment descriptor, web.xml. The specifics on this modification are detailed in the next section of this document.

The delivered Maven POM file depends on the original WebAdvisor WAR file to be in the project's lib subdirectory. In the project that Unicon delivered to ESC that file was lib/DEVSSOPO.war. The modified WAR file ends up in target/DEVSSOPO.war. Obviously, these names can be changed in pom.xml.

When everything is set up, execute mvn package. The build process is relatively quick.

Configuring the Filter

The filter's main configuration must be added to WebAdvisor's web.xml file. Start by extracting this file from the original WebAdvisor WAR file, DEVSSOPO.war in this project, and placing it in src/main/webapp/WEB-INF. Next, open doc/addition to web.xml and make sure that all of the configuration options are set correctly. The contents of this file are below:

  <!-- Added by arybicki@unicon.net -->

  <filter>
    <filter-name>CAS Authentication Filter</filter-name>
    <filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class>
    <init-param>
      <param-name>casServerLoginUrl</param-name>
      <param-value>https://login.esc.edu/cas/login</param-value>
    </init-param>
    <init-param>
      <param-name>serverName</param-name>
      <param-value>https://webadvsrv.esc.edu</param-value>
    </init-param>
  </filter>
  
  <filter>
    <filter-name>CAS Validation Filter</filter-name>
    <filter-class>org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter</filter-class>
    <init-param>
      <param-name>casServerUrlPrefix</param-name>
      <param-value>https://login.esc.edu/cas</param-value>
    </init-param>
    <init-param>
      <param-name>serverName</param-name>
      <param-value>https://webadvsrv.esc.edu</param-value>
    </init-param>
    <init-param>
      <param-name>proxyCallbackUrl</param-name>
      <param-value>https://webadvsrv.esc.edu/DEVSSOPO/CasProxyServlet</param-value>
    </init-param>
    <init-param>
      <param-name>proxyReceptorUrl</param-name>
      <param-value>/CasProxyServlet</param-value>
    </init-param>
  </filter>
  
  <filter>
    <filter-name>WebAdvisor Authentication Filter</filter-name>
    <filter-class>edu.esc.cas.client.webadvisor.filter.WebAdvisorLoginFilter</filter-class>
    <init-param>
      <param-name>log4jLocation</param-name>
      <param-value>classpath:log4j.xml</param-value>
    </init-param>
    <init-param>
      <param-name>clearPassURL</param-name>
      <param-value>https://login.esc.edu/cas/clearPass</param-value>
    </init-param>
    <init-param>
      <param-name>loginPattern</param-name>
      <param-value>SS=LGRQ</param-value>  <!-- This is what the filter uses to trigger redirect to CAS authentication -->
    </init-param>
    <init-param>
      <param-name>logoutPattern</param-name>
      <param-value>pid=UT-LORQ</param-value>  <!-- This is what the filter uses to trigger removal of CAS assertion -->
    </init-param>
    <init-param>
      <param-name>loginURI</param-name>
      <param-value>/DEVSSOPO/Login</param-value>  <!-- This is where the filter redirects to to perform authentication -->
    </init-param>
    <init-param>
      <param-name>webadvisorSSOURL</param-name>
      <param-value>https%3A%2F%2Fwebadvsrv.esc.edu%2FDEVSSOPO%2Fsso%3FCONSTITUENCY%3DWBST%26type%3DP%26pid%3DST-XWESTGRADE</param-value>  <!-- This is the endpoint to which the request for user's SSO token will be sent -->
    </init-param>
    <init-param>
      <param-name>webadvisorLoginURL</param-name>
      <param-value>https%3A%2F%2Fwebadvsrv.esc.edu%2FDEVSSOPO%2Fst%3FCONSTITUENCY%3DWBST%26type%3DP%26pid%3DST-XWESTGRADE</param-value>  <!-- This is where the SSO token will be sent for authentication -->
    </init-param>
    <init-param>
      <param-name>mainMenuURI</param-name>
      <param-value>/DEVSSOPO/</param-value>  <!-- This is where the filter redirects to after WebAdvisor login -->
    </init-param>
    <init-param>
      <param-name>browserHeadersToForward</param-name>  <!-- Forward these HTTP headers with the WebAdvisor login -->
      <param-value>cookie host user-agent accept accept-language accept-encoding accept-charset</param-value>
    </init-param>
    <init-param>
      <param-name>webAdvisorHeadersToForward</param-name>  <!-- Return these WebAdvisor HTTP headers after authentication -->
      <param-value>Set-Cookie Content-Type Content-Length</param-value>
    </init-param>
  </filter>
  
  <filter-mapping>
    <filter-name>CAS Validation Filter</filter-name>
    <url-pattern>/CasProxyServlet</url-pattern>
  </filter-mapping>

  <filter-mapping>
    <filter-name>CAS Authentication Filter</filter-name>
    <url-pattern>/Login</url-pattern>
  </filter-mapping>
  
  <filter-mapping>
    <filter-name>CAS Validation Filter</filter-name>
    <url-pattern>/Login</url-pattern>
  </filter-mapping>
  
  <filter-mapping>
    <filter-name>WebAdvisor Authentication Filter</filter-name>  <!-- This filter must come AFTER the Jasig CAS Client filters above -->
    <url-pattern>/*</url-pattern>
  </filter-mapping>

  <!-- End of the addition by arybicki@unicon.net -->

With the file open in a text editor, copy the entire file to clipboard, and paste it into src/main/webapp/WEB-INF/web.xml near the bottom and just above the </web-app> tag.

The only other place to configure the filter is the filter's logging. This also configures logging of the Jasig CAS Client for Java. The configuration file is src/main/webapp/WEB-INF/classes/log4j.xml and it looks like this:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">

<log4j:configuration debug="false" xmlns:log4j="http://jakarta.apache.org/log4j/">
  <!--
      This default ConsoleAppender is used to log all NON perf4j messages
      to System.out
    -->
  <appender name="console" class="org.apache.log4j.ConsoleAppender">
    <layout class="org.apache.log4j.PatternLayout">
      <param name="ConversionPattern" value="%d %p [%c] - &lt;%m&gt;%n" />
    </layout>
  </appender>

  <appender name="casclient" class="org.apache.log4j.RollingFileAppender">
    <param name="File" value="${catalina.base}/logs/casclient.log" />
    <param name="MaxFileSize" value="512KB" />
    <param name="MaxBackupIndex" value="3" />
    <layout class="org.apache.log4j.PatternLayout">
      <param name="ConversionPattern" value="%d %p [%c] - %m%n" />
    </layout>
  </appender>

  <logger name="org.jasig" additivity="true">
    <level value="DEBUG" />
    <appender-ref ref="casclient" />
  </logger>

  <logger name="edu.esc.cas" additivity="true">
    <level value="DEBUG" />
    <appender-ref ref="casclient" />
  </logger>

  <!--
      The root logger sends all log statements to System.out.
    -->
  <root>
    <level value="ERROR" />
    <appender-ref ref="console" />
  </root>
</log4j:configuration>

The example above places the filter's log in file casclient.log in Tomcat's logs sub-directory. For production installations, Unicon recommends to change the log level settings set to DEBUG in the example above to INFO.

  File Modified

Java Source WebAdvisorLoginFilter.java

Mar 01, 2011 by Adam Rybicki

File ssoError.jsp

Mar 01, 2011 by Adam Rybicki

XML File pom.xml

Mar 01, 2011 by Adam Rybicki

Eclipse Project

You may be able to use this eclipse project as a starting point.

 

Error Reporting

In case of unrecoverable errors, the filter displays a fairly generic error message. Unicon encourages implementers to improve the looks of this message because it will be displayed to end users in cases of unrecoverable errors. The error page does not and should not include details about the error's cause. Since this is a security-sensitive application, adding details to error messages could aid adversaries. Instead, errors are added to the log file.