Installation Instructions
Second-Level CAS Server is not much different from any CAS server. Second-level authentication occurs simply because the second-level server is itself CAS-enabled, and requires authentication to the primary CAS server to be accessed in the first place. All Web applications that require second-level authentication are configured to authenticate to the Second-Level CAS Server.
The beauty of this approach is that the Web applications requiring second-level authentication do not need any special configuration. They are simply CAS-enabled, and they are configured to validate CAS tickets to the Second-Level CAS Server. Further, the primary CAS server operates just the same. It is not aware that Second-Level CAS Server is any different from all other Web applications that authenticate to it and validate their tickets.
Second-Level CAS Server implementation at UC Berkeley uses Single Sign-Out to enforce two-level security. This is where most of the Second-Level CAS Server "smarts" comes in. If the user logs out of the primary CAS server, Second-Level CAS Server also invalidates its TGC representing the user who just logged out. If, on the other hand, the user logs out from the Second-Level CAS Server, the user's primary CAS server TGC is still valid. Second-Level CAS Server may be configured to send Single Sign-Out messages to the services that acquired service tickets from Second-Level CAS Server. Configuring Single Sign-Out is beyond the scope of this document, as it is no different from single-server implementation.
The distribution of Second-Level CAS Server is a drop-in package that adds to a standard CAS sever distribution. To build Second-Level CAS Server, expand the package into a distribution of CAS Server 3.2 or higher. This should add another module to the CAS server. To assure that the new module is included in the build, two steps are required. First, add it to the list of modules to the top-level pom.xml to looks like this:
<modules> <module>cas-server-core</module> <module>cas-server-support-generic</module> <module>cas-server-support-jdbc</module> <module>cas-server-support-ldap</module> <module>cas-server-support-legacy</module> <module>cas-server-support-openid</module> <module>cas-server-support-radius</module> <module>cas-server-support-spnego</module> <module>cas-server-support-second-level</module> <!-- New module --> <module>cas-server-support-trusted</module> <module>cas-server-support-x509</module> <module>cas-server-integration-jboss</module> <module>cas-server-integration-berkeleydb</module> <module>cas-server-webapp</module> </modules>
This will build the module, but to include it in the cas.war file, you have to add it as a dependency to pom.xml responsible for building cas.war. You can do this by adding the following fragment to the Dependencies element of pom.xml in the cas-server-webapp module. Since Second-Level CAS Server also requires the JA-SIG Java CAS Client, its dependency is added here as well. Since this solution needs to work in a clustered environment, JBoss cache support is required. Lastly, the custom authentication handler that UC Berkeley uses requires LDAP support.
<dependency> <groupId>org.jasig.cas</groupId> <artifactId>cas-client-core</artifactId> <version>3.1.3</version> </dependency> <dependency> <groupId>${project.groupId}</groupId> <artifactId>cas-server-support-second-level</artifactId> <version>${project.version}</version> </dependency> <dependency> <groupId>${project.groupId}</groupId> <artifactId>cas-server-integration-jboss</artifactId> <version>${project.version}</version> <type>jar</type> </dependency> <dependency> <groupId>${project.groupId}</groupId> <artifactId>cas-server-support-ldap</artifactId> <version>${project.version}</version> </dependency>
Second-Level CAS Server uses the JA-SIG Java CAS Client. It is configured in the deployment descriptor, web.xml. Below are the additions required to accomplish two things:
- Second-Level CAS Server is itself CAS-enabled and requires primary CAS authentication
- Second-Level CAS Server accepts and handles Single Sign-Out messages from the primary CAS server
<!-- JA-SIG CAS client for Java --> <!-- First, the single sign-on filters --> <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://primarycas.university.edu/cas/login</param-value> </init-param> <init-param> <param-name>serverName</param-name> <param-value>https://secondarycas.university.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://primarycas.university.edu/cas</param-value> </init-param> <init-param> <param-name>redirectAfterValidation</param-name> <param-value>true</param-value> </init-param> <init-param> <param-name>serverName</param-name> <param-value>https://secondarycas.university.edu</param-value> </init-param> </filter> <filter> <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name> <filter-class>org.jasig.cas.client.util.HttpServletRequestWrapperFilter</filter-class> </filter> <filter> <filter-name>CAS Assertion Thread Local Filter</filter-name> <filter-class>org.jasig.cas.client.util.AssertionThreadLocalFilter</filter-class> </filter> <!-- And now, the single sign-out filters --> <filter> <filter-name>CAS Single Sign Out Filter</filter-name> <filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class> </filter> <filter> <filter-name>Primary CAS Single Sign Out Filter</filter-name> <filter-class>edu.berkeley.cas.web.filter.PrimaryCASSingleSignOutFilter</filter-class> </filter> <!-- Filter mappings in the correct order --> <filter-mapping> <filter-name>Primary CAS Single Sign Out Filter</filter-name> <url-pattern>/login/*</url-pattern> </filter-mapping> <filter-mapping> <filter-name>CAS Single Sign Out Filter</filter-name> <url-pattern>/login/*</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>CAS HttpServletRequest Wrapper Filter</filter-name> <url-pattern>/login/*</url-pattern> </filter-mapping> <filter-mapping> <filter-name>CAS Assertion Thread Local Filter</filter-name> <url-pattern>/login/*</url-pattern> </filter-mapping> <listener> <listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class> </listener>
Second-Level CAS Server is mostly configured inside its own Spring Beans configuration file. This file is included in the package under src/main/resources, and its name is secondLevelCAS.xml. In order to take effect, this file needs to be copied to cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration.
If you choose to implement Second-Level CAS Server authentication with PIN pad functionality like UC Berkeley, the following changes may be required.
The PIN pad functionality is implemented in a different view. It is implemented in calNetKeyLoginView.jsp, and after adding it to cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui it is enabled by making a configuration change in cas-server-webapp/src/main/webapp/WEB-INF/classes/default_views.properties from this:
### Login view (/login) casLoginView.(class)=org.springframework.web.servlet.view.JstlView casLoginView.url=/WEB-INF/view/jsp/default/ui/casLoginView.jsp
to this
### Login view (/login) casLoginView.(class)=org.springframework.web.servlet.view.JstlView casLoginView.url=/WEB-INF/view/jsp/default/ui/calNetKeyLoginView.jsp
This new view requires additional resources: some JavaScript and couple of images. Copy pinpad.js to cas-server-webapp/src/main/webapp/js and the two image files to cas-server-webapp/src/main/webapp/images.
Since the PIN pad-based CAS uses a different credential, a custom CredentialBinder class had to be implemented. It is configured in cas-server-webapp/src/main/webapp/WEB-INF/cas-servlet.xml by changing the declaration of the authenticationViaFormAction bean from this:
<bean id="authenticationViaFormAction" class="org.jasig.cas.web.flow.AuthenticationViaFormAction" p:centralAuthenticationService-ref="centralAuthenticationService" p:warnCookieGenerator-ref="warnCookieGenerator" />
to this:
<bean id="authenticationViaFormAction" class="org.jasig.cas.web.flow.AuthenticationViaFormAction" p:centralAuthenticationService-ref="centralAuthenticationService" p:warnCookieGenerator-ref="warnCookieGenerator" p:credentialsBinder-ref="calNetKeyCredentialsBinder" p:formObjectClass="edu.berkeley.cas.authentication.principal.CalNetKeyCredentials" p:formObjectName="credentials" />
And finally, cas-server-webapp/src/main/webapp/WEB-INF/deployerConfigContext.xml needs to be changed to use the UC Berkeley-specific authentication handler and principal resolver. The principal resolver needs to be changed from this:
<bean class="org.jasig.cas.authentication.principal.UsernamePasswordCredentialsToPrincipalResolver" />
to this:
<bean class="edu.berkeley.cas.authentication.principal.CalNetKeyCredentialsToPrincipalResolver" />
Here is a fragment of this file that contains the configuration of the new authentication handler:
<bean class="edu.berkeley.cas.adaptors.ldap.CalNetKeyAuthenticationHandler"> <property name="filter" value="uid=%u" /> <property name="searchBase" value="ou=people,dc=university,dc=edu" /> <!-- SearchControls.OBJECT_SCOPE=0, SearchControls.ONELEVEL_SCOPE=1, SearchControls.SUBTREE_SCOPE=2 --> <property name="scope" value="1" /> <property name="allowMultipleAccounts" value="false" /> <property name="pinAttribute" value="berkeleyEduCalNetKey" /> <property name="saltAttribute" value="berkeleyEduCalNetKeySalt" /> <property name="contextSource" ref="contextSource" /> </bean>
The bean contextSource is a standard LDAP CAS context source. A sample configuration is below:
<bean id="contextSource" class="org.jasig.cas.adaptors.ldap.util.AuthenticatedLdapContextSource"> <property name="anonymousReadOnly" value="false" /> <property name="userName" value="[fully-qualified DN of a user]" /> <property name="password" value="[removed]" /> <property name="pooled" value="true" /> <property name="urls"> <list> <value>ldaps://ldap/university.edu/</value> </list> </property> <property name="baseEnvironmentProperties"> <map> <entry> <key><value>java.naming.security.authentication</value></key> <value>simple</value> </entry> </map> </property> </bean>