...
The contents of cas.properties should be exactly the same as that distributed with the CAS distribution:
Code Block |
---|
| xml |
---|
| xml |
---|
title | cas.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 |
---|
title | applicationContext.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 |
---|
title | uniqueIdGenerators.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 |
---|
|
...
<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 |
---|
title | Tomcat 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 |
---|
title | Tomcat 6.x server.xml | xml |
---|
|
<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 |
---|
title | applicationContext.xml | xml |
---|
|
<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 |
---|
|
...
<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 |
---|
|
<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 |
---|
title | Protect 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 |
---|
title | warnCookieGenerator.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 |
---|
title | ticketGrantingCookieGenerator.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" />
|
...