Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Migrated to Confluence 5.3

...

The contents of cas.properties should be exactly the same as that distributed with the CAS distribution:

Code Block
xml
xml
titlecas.propertiesxml
# Unique name for each node in cluster
# Host name is a good choice, but can be anything
host.name=eiger

# Security settings
cas.securityContext.serviceProperties.service=https://eiger.some.edu/cas/services/j_acegi_cas_security_check
# Names of roles allowed to access the CAS service manager
cas.securityContext.serviceProperties.adminRoles=ROLE_MIDDLEWARE.STAFF
cas.securityContext.casProcessingFilterEntryPoint.loginUrl=https://eiger.some.edu/cas/login
cas.securityContext.ticketValidator.casServerUrlPrefix=https://eiger.some.edu/cas

# Name of properties file defining views
cas.viewResolver.basename=default

In order for CAS to load properties from the filesystem instead of the classpath of the unpacked WAR file, you must modify the file /WEB-INF/applicationContext.xml.

Code Block
xml
xml
titleapplicationContext.xmlxml
<bean id="placeholderConfig"
      class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
      <property name="locations">
            <list>
                  <value>file:/apps/local/share/etc/cas.properties</value>
             </list>
      </property>
</bean>

The host.name property placeholder is used by ticket generators to tag tickets issued by a particular cluster node:

Code Block
xml
xml
titleuniqueIdGenerators.xmlxml
  <bean id="ticketGrantingTicketUniqueIdGenerator" class="org.jasig.cas.util.DefaultUniqueTicketIdGenerator">
    <constructor-arg
      index="0"
      type="int"
      value="50" />
    <constructor-arg
      index="1" value="${host.name}" />
  </bean>

  <bean id="serviceTicketUniqueIdGenerator" class="org.jasig.cas.util.DefaultUniqueTicketIdGenerator">
    <constructor-arg
      index="0"
      type="int"
      value="20" />
    <constructor-arg
      index="1" value="${host.name}" />
  </bean>
  
  <bean
    id="proxy20TicketUniqueIdGenerator"
    class="org.jasig.cas.util.DefaultUniqueTicketIdGenerator">
  <constructor-arg
    index="0"
    type="int"
    value="20" />
  <constructor-arg
    index="1" value="${host.name}" />
  </bean>
...

...

Since CAS stores the login information in the application session we need to setup session replication between our Tomcat instances.

Warning

Note there was an approach (sometimes referenced in older resources) for preserving application login state via a Spring Workflow 1.0 configuration option (Spring 1.0 documentation on this here). Spring Webflow 2.0+ (used in modern versions of CAS) no longer has this feature, meaning this state must be maintained in some other way (such as Tomcat session replication covered here).

The first thing you need to do is tell CAS (the application) that it is distributable 1. So, in the CAS web.xml file you need to add the <distributable/> tag. The web.xml file is located here:

...

In this file, I put the distributable tag right below the context-param section:

Code Block
xml
xml
titleweb.xmlxml
...
<context-param>
      <param-name>contextConfigLocation</param-name>
            <param-value>
                  /WEB-INF/applicationContext.xml,
                  /WEB-INF/deployerConfigContext.xml
             </param-value>
</context-param>

<!-- Set the application as distributable: http://tomcat.apache.org/tomcat-5.0-doc/cluster-howto.html -->
<distributable />
...

Now you need to tell Tomcat to replicate the session information by adding Cluster elements under the Host elements. In the following examples, data is replicated via UDP multicast since it requires the least amount of host-specific configuration. An alternative is to use TCP, where each node must explicitly know about its peers. Regardless of your choice, you should thoroughly test node failure with your replication strategy to determine whether your network supports graceful node loss and recovery.

Code Block
xml
xml
titleTomcat 5.5.x server.xmlxml
  <Server port="8005" shutdown="SHUTDOWN">
    ...
    <Engine name="Standalone" defaultHost="localhost">
      ...
      <Host name="localhost" deployOnStartup="true" autoDeploy="false" appBase="webapps">
        ...
        <!--
            When configuring for clustering, you also add in a valve to catch all the requests
            coming in, at the end of the request, the session may or may not be replicated.
            A session is replicated if and only if all the conditions are met:
            1. useDirtyFlag is true or setAttribute or removeAttribute has been called AND
            2. a session exists (has been created)
            3. the request is not trapped by the "filter" attribute

            The filter attribute is to filter out requests that could not modify the session,
            hence we don't replicate the session after the end of this request.
            The filter is negative, ie, anything you put in the filter, you mean to filter out,
            ie, no replication will be done on requests that match one of the filters.
            The filter attribute is delimited by ;, so you can't escape out ; even if you wanted to.

            filter=".*\.gif;.*\.js;" means that we will not replicate the session after requests
            with the URI ending with .gif and .js are intercepted.
        -->
        <Cluster className="org.apache.catalina.cluster.tcp.SimpleTcpCluster"
                 managerClassName="org.apache.catalina.cluster.session.DeltaManager"
                 expireSessionsOnShutdown="false"
                 useDirtyFlag="true">

            <Membership
                className="org.apache.catalina.cluster.mcast.McastService"
                mcastAddr="239.255.0.1"
                mcastPort="45564"
                mcastFrequency="500"
                mcastDropTime="3000"
                mcastTTL="1"/>

            <Receiver
                className="org.apache.catalina.cluster.tcp.ReplicationListener"
                tcpListenAddress="auto"
                tcpListenPort="4001"
                tcpSelectorTimeout="100"
                tcpThreadCount="6"/>

            <Sender
                className="org.apache.catalina.cluster.tcp.ReplicationTransmitter"
                replicationMode="synchronous"/>

            <Valve className="org.apache.catalina.cluster.tcp.ReplicationValve"
                   filter=".*\.gif;.*\.js;.*\.jpg;.*\.htm;.*\.html;.*\.txt;"/>

            <ClusterListener className="org.apache.catalina.cluster.session.ClusterSessionListener"/>

        </Cluster>
      </Host>
    </Engine>
  </Server>
Code Block
xml
xml
titleTomcat 6.x server.xmlxml
  <Server port="8005" shutdown="SHUTDOWN">
    ...
    <Engine name="Catalina" defaultHost="localhost">
      ...
      <Host name="localhost" appBase="webapps"
        unpackWARs="true" autoDeploy="true"
        xmlValidation="false" xmlNamespaceAware="false">
        ...
        <!--
            When configuring for clustering, you also add in a valve to catch all the requests
            coming in, at the end of the request, the session may or may not be replicated.
            A session is replicated if and only if all the conditions are met:
            1. useDirtyFlag is true or setAttribute or removeAttribute has been called AND
            2. a session exists (has been created)
            3. the request is not trapped by the "filter" attribute

            The filter attribute is to filter out requests that could not modify the session,
            hence we don't replicate the session after the end of this request.
            The filter is negative, ie, anything you put in the filter, you mean to filter out,
            ie, no replication will be done on requests that match one of the filters.
            The filter attribute is delimited by ;, so you can't escape out ; even if you wanted to.

            filter=".*\.gif;.*\.js;" means that we will not replicate the session after requests with the URI
            ending with .gif and .js are intercepted.
        -->
        <Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster">

          <Manager className="org.apache.catalina.ha.session.DeltaManager"
                   expireSessionsOnShutdown="false"
                   notifyListenersOnReplication="true"/>

          <Channel className="org.apache.catalina.tribes.group.GroupChannel">
            <Membership
                className="org.apache.catalina.tribes.membership.McastService"
                address="239.255.0.1"
                port="45564"
                frequency="500"
                dropTime="3000"
                mcastTTL="1"/>

            <Receiver
                className="org.apache.catalina.tribes.transport.nio.NioReceiver"
                address="auto"
                port="4000"
                autoBind="0"
                selectorTimeout="100"
                maxThreads="6"/>

            <Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter">
              <Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender"/>
            </Sender>
            <Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector"/>
            <Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatch15Interceptor"/>

          </Channel>

          <Valve className="org.apache.catalina.ha.tcp.ReplicationValve"
                   filter=".*\.gif;.*\.js;.*\.jpg;.*\.htm;.*\.html;.*\.txt;"/>

          <ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/>
        </Cluster>
      </Host>
    </Engine>
  </Server>

...

Now you we need to setup the ticket cache replication using the org.jasig.cas.ticket.registry.JBossCacheTicketRegistry class. We implement this by editing the applicationContext.xml config file again.

Code Block
xml
xml
titleapplicationContext.xmlxml
<bean id="ticketRegistry"
  class="org.jasig.cas.ticket.registry.JBossCacheTicketRegistry"
  p:cache-ref="cache" />

<bean id="cache" class="org.jasig.cas.util.JBossCacheFactoryBean"
  p:configLocation="classpath:jbossTicketCacheReplicationConfig.xml" />

...

Remarks: The dependency is needed if you are NOT using JBoss Application Server.

Code Block
xml
xml
titlepom.xmlxml
...
<dependency>
	<groupId>org.jasig.cas</groupId>
	<artifactId>cas-server-integration-jboss</artifactId>
	<version>3.1</version>
	<scope>runtime</scope>
</dependency>
...

...

You need to exclude some jars from the deployment otherwise they will conflict with JBOSS.

Code Block
xml
xml
titlepom.xmlxml
<dependency>
            <groupId>org.jasig.cas</groupId>
            <artifactId>cas-server-integration-jboss</artifactId>
            <version>3.2.1-RC1</version>
            <scope>runtime</scope>
            <exclusions>
                <exclusion>
                        <groupId>concurrent</groupId>
                        <artifactId>concurrent</artifactId>
                </exclusion>
                <exclusion>
                        <groupId>jboss</groupId>
                        <artifactId>jboss-serialization</artifactId>
                </exclusion>

                <exclusion>
                        <groupId>jboss</groupId>
                        <artifactId>jboss-jmx</artifactId>
                </exclusion>
                <exclusion>
                        <groupId>jboss</groupId>
                        <artifactId>jboss-common</artifactId>
                </exclusion>
                <exclusion>
                        <groupId>jboss</groupId>
                        <artifactId>jboss-j2ee</artifactId>
                </exclusion>
                <exclusion>
                        <groupId>jboss</groupId>
                        <artifactId>jboss-minimal</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>jboss</groupId>
                    <artifactId>jboss-system</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

...

Warning
titleProtect your ticket granting cookies!

Warning: do not set the cookieDomain any wider than absolutely necessary. All hosts in the cookieDomain must be absolutely trusted - at a security level of your CAS server itself. Ideally all clustered CAS server instances will appear to the end user's web browser to be answering the very same URLs (e.g., the cluster is fronted by a hardware load balancer) and so the cookieDomain can be maximally restrictive.

Setting the cookie domain such that untrusted servers have access to the Ticket Granting Cookie will allow those servers to hijack the end user's single sign on session and acquire service tickets in his or her name to illicitly authenticate to CASified applications.

Code Block
xml
xml
titlewarnCookieGenerator.xmlxml
<bean id="warnCookieGenerator"
  class="org.jasig.cas.web.support.CookieRetrievingCookieGenerator"
  p:cookieSecure="true"
  p:cookieMaxAge="-1"
  p:cookieName="CASPRIVACY"
  p:cookiePath="/cas"
  p:cookieDomain="example.com"/>
Code Block
xml
xml
titleticketGrantingCookieGenerator.xmlxml
<bean id="ticketGrantingTicketCookieGenerator"
  class="org.jasig.cas.web.support.CookieRetrievingCookieGenerator"
  p:cookieSecure="true"
  p:cookieMaxAge="-1"
  p:cookieName="CASTGC"
  p:cookiePath="/cas"
  p:cookieDomain="example.com" />

...