Versions Compared

Key

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

This tutorial demonstrates Active Directory authentication, MySQL-based registries, LDAP attribute release and single sign-off using the 3.4.2.1 server and 3.1.10 client running on Windows XP systems. The Maven overlay method is used to build the server.

Overview

This document describes work I did to evaluate CAS. This is intended to be a beginner's introduction. I'm not a member of the CAS team and I don't have a lot of experience with the system, so I imagine there are more efficient ways to go about this - feel free to correct errors or improve the results!

...

Once installed, edit C:\apache-tomcat-6.0.29\conf\tomcat-users.xml to add a privileged user so you can access the tomcat console.

No Format

<user username="admin" password="password" roles="manager"/>

...

On the SSO server, import the test app server's certificate into the cacerts trust store, which is located here:

No Format

C:\Program Files\Java\jdk1.6.0_20\jre\lib\security\cacerts

...

The connector element below, for the SSO server, shows that I copied my keystore into C:\apache-tomcat-6.0.29\conf. Make sure the password and alias match what was used to create the keystore and cert.

No Format

<Connector port="8443" protocol="org.apache.coyote.http11.Http11Protocol"
   SSLEnabled="true" maxThreads="150" scheme="https" secure="true"
   keystoreFile="conf/ssoServer.jks" keyAlias="tomcat" keystorePass="changeit"
   clientAuth="false" sslProtocol="TLS"
/>

...

Create the ticket registry locks table with the following columns:

No Format

CREATE TABLE LOCKS (
  APPLICATION_ID VARCHAR(50) NOT NULL,
  UNIQUE_ID VARCHAR(50) NULL,
  EXPIRATION_DATE TIMESTAMP NULL
);
ALTER TABLE LOCKS ADD CONSTRAINT LOCKS_PK
PRIMARY KEY (APPLICATION_ID);

...

On your server build system, create a folder to hold your local project, e.g., this demo uses C:\CAS_SSO\local-cas. Create a file, pom.xml, in that directory and copy in this xml:

No Format
titlepom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.merlin</groupId>
  <artifactId>local-cas</artifactId>
  <packaging>war</packaging>
  <version>1.0-SNAPSHOT</version>
  <build>
    <plugins>
      <plugin>
        <artifactId>maven-war-plugin</artifactId>
          <configuration>
             <warName>cas</warName>
          </configuration>
      </plugin>
    </plugins>
  </build>
  <dependencies>
    <dependency>
      <groupId>org.jasig.cas</groupId>
      <artifactId>cas-server-webapp</artifactId>
      <version>${cas.version}</version>
      <type>war</type>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>org.jasig.cas</groupId>
      <artifactId>cas-server-support-ldap</artifactId>
      <version>${cas.version}</version>
    </dependency>
    <dependency>
      <groupId>commons-dbcp</groupId>
      <artifactId>commons-dbcp</artifactId>
      <version>1.4</version>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.13</version>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-entitymanager</artifactId>
      <version>3.5.0-CR-2</version>
      <scope>runtime</scope>
      <type>jar</type>
    </dependency>
    <dependency>
      <groupId>concurrent</groupId>
      <artifactId>concurrent</artifactId>
      <version>1.3.4</version>
      <scope>runtime</scope>
    </dependency>
  </dependencies>
  <properties>
    <cas.version>3.4.2.1</cas.version>
  </properties>
  <repositories>
    <repository>
      <id>ja-sig</id>
      <url>http://oss.sonatype.org/content/repositories/releases/</url>
    </repository>
    <repository>
      <id>jboss</id>
      <url>http://repository.jboss.org/nexus/content/groups/public-jboss/</url>
    </repository>
  </repositories>
</project>

...

This build just downloads the initial set of components, including the configuration files we'll use to create the server system. Open a console window and navigate to C:\CAS_SSO\local-cas and execute the maven command to build the project:

No Format

mvn clean package -DskipTests=true

...

Navigate into the target directories and copy the configuration files (highlighted in the image) into their corresponding src sub-directories as listed below:

No Format

C:\CAS_SSO\local-cas\src\main\webapp\WEB-INF
   cas.properties
   deployerConfigContext.xml

C:\CAS_SSO\local-cas\src\main\webapp\WEB-INF\spring-configuration
   ticketRegistry.xml

...

Set up the Services Management URL, and the database platform type of the services registry.

No Format

# Point to the server that hosts Services Management
cas.securityContext.serviceProperties.service=http://localhost:8080/cas/services/j_acegi_cas_security_check
cas.securityContext.casProcessingFilterEntryPoint.loginUrl=http://localhost:8080/cas/login
cas.securityContext.ticketValidator.casServerUrlPrefix=http://localhost:8080/cas

# Names of roles allowed to access the CAS service manager
cas.securityContext.serviceProperties.adminRoles=ROLE_ADMIN

# The database platform is "SQL92" since MySQL meets that standard
ticket.cleaner.database.platform=SQL92
database.hibernate.dialect=org.hibernate.dialect.MySQLDialect

host.name=cas
cas.themeResolver.defaultThemeName=default
cas.viewResolver.basename=default_views

I'm not sure which host 'host.name' refers to - I left it at the default value, cas, and everything seems to workHost.name is the public domain issued and should match any https/ssl cert used.

Edit deployerConfigContext.xml

...

This object provides connections to the LDAP. Add as many URLs as desired to the list. Set the userDn and password to the credentials of a service account created for user lookups.

No Format

<bean id="contextSource" class="org.springframework.ldap.core.support.LdapContextSource">
  <property name="anonymousReadOnly" value="false" />
  <property name="userDn" value="CN=lookupAcct,CN=Users,DC=merlin,DC=com" />
  <property name="password" value="secret" />
  <property name="pooled" value="true" />
  <property name="urls">
    <list>
      <value>ldap://ssoserver.merlin.com/</value>
    </list>
  </property>
  <property name="baseEnvironmentProperties">
    <map>
      <entry>
        <key><value>java.naming.security.authentication</value></key>
        <value>simple</value>
      </entry>
    </map>
  </property>
</bean>

...

We will pass LDAP user attributes to the client. To do so we declare an attribute repository and, since the repository is our LDAP, we tell it to use the context source defined above. Edit the baseDN as needed for your domain.

No Format

<bean id="attributeRepository"
  class="org.jasig.services.persondir.support.ldap.LdapPersonAttributeDao">

  <property name="contextSource" ref="contextSource" />
  <property name="baseDN" value="DC=merlin,DC=com" />
  <property name="requireAllQueryAttributes" value="true" />
  <property name="ldapTemplate" ref="ldapTemplate" />

  <!--
  Attribute mapping between principal (key) and LDAP (value) names
  used to perform the LDAP search.
  -->
  <property name="queryAttributeMapping">
    <map>
      <entry key="username" value="sAMAccountName" />
    </map>
  </property>

  <property name="resultAttributeMapping">
    <map>
      <!-- Mapping between LDAP attributes (key) and Principal's (value) -->
      <entry value="CN" key="cn" />
      <entry value="DN" key="distinguishedName" />
      <entry value="Groups" key="memberOf" />
    </map>
  </property>
</bean>

<bean id="ldapTemplate" class="org.springframework.ldap.core.LdapTemplate">
  <constructor-arg ref="contextSource" />
  <property name="ignorePartialResultException" value="true" />
</bean>

...

The authenticationManager bean has a list of credentialsToPrincipalResolvers. This adds one that will do an LDAP lookup based on the sAMAccountName entered by the user, and will define the Principal available to the client application. This uses the context source and the attribute repository defined above. Edit the searchBase as needed for your directory's structure.

No Format

<bean
  class="org.jasig.cas.authentication.principal.CredentialsToLDAPAttributePrincipalResolver">

  <!-- The Principal resolver form the credentials -->
  <property name="credentialsToPrincipalResolver">
    <bean
    class="org.jasig.cas.authentication.principal.UsernamePasswordCredentialsToPrincipalResolver" />
  </property>

  <!-- "%u" will be replaced by the resolved Principal -->
  <property name="filter" value="(sAMAccountName=%u)" />

  <!-- The attribute used to define the new Principal ID -->
  <property name="principalAttributeName" value="sAMAccountName" />

  <property name="searchBase" value="DC=merlin,DC=com" />
  <property name="contextSource" ref="contextSource" />

  <property name="attributeRepository">
    <ref bean="attributeRepository" />
  </property>
</bean>

...

The authenticationManager bean has a list of authenticationHandlers. This adds one that, once user lookup has been completed, will authenticate the user by opening a context using the retrieved distinguished name and the entered password. This bean also uses the context source defined above. Edit the searchBase as needed for your directory's structure.

No Format

<bean class="org.jasig.cas.adaptors.ldap.BindLdapAuthenticationHandler">
  <property name="filter" value="sAMAccountName=%u" />
  <property name="searchBase" value="DC=merlin,DC=com" />
  <property name="contextSource" ref="contextSource" />
  <property name="ignorePartialResultException" value="yes" /> <!-- handle AD partial results -->
</bean>

...

The demo's service registry is in a MySQL database accessed via the Java Persistence Architecture (JPA) and Hibernate. The following beans specify the database type, enable automatic table creation and provide for connections to the database. Some of these beans will be shared by the ticket registry and referenced from the ticketRegistry.xml file. Edit the dataSource username and password properties as needed.  (Edit deployerConfigContext.xml)

Note

You must add the "tx" namespace to the top of the file!

No Format

<bean id="serviceRegistryDao"
  class="org.jasig.cas.services.JpaServiceRegistryDaoImpl"
  p:entityManagerFactory-ref="entityManagerFactory" />

<bean id="entityManagerFactory"
  class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">

  <property name="dataSource" ref="dataSource"/>
  <property name="jpaVendorAdapter">
    <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
      <property name="generateDdl" value="true"/>
      <property name="showSql" value="true" />
    </bean>
  </property>
  <property name="jpaProperties">
  <props>
    <prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
    <prop key="hibernate.hbm2ddl.auto">update</prop>
  </props>
  </property>
</bean>

<bean id="transactionManager"
  class="org.springframework.orm.jpa.JpaTransactionManager">

  <property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>

<tx:annotation-driven transaction-manager="transactionManager"/>

<bean id="dataSource"
  class="org.apache.commons.dbcp.BasicDataSource"
  p:driverClassName="com.mysql.jdbc.Driver"
  p:url="jdbc:mysql://localhost:3306/cas_sso?autoReconnect=true"
  p:password="secret"
  p:username="root" />

...

Enter the LDAP sAMAccountNames of users who will be able to run the CAS Services Management. The password is not used so just enter 'notused'.

No Format

<sec:user-service id="userDetailsService">
<sec:user name="ndoe" password="notused" authorities="ROLE_ADMIN" />
</sec:user-service>

...

Replace the file's contents with the following or use this attached file.

No Format

<bean id="ticketRegistry" class="org.jasig.cas.ticket.registry.JpaTicketRegistry">
  <constructor-arg index="0" ref="entityManagerFactory" />
</bean>

<bean id="ticketRegistryCleaner"
  class="org.jasig.cas.ticket.registry.support.DefaultTicketRegistryCleaner"
  p:ticketRegistry-ref="ticketRegistry"
  p:lock-ref="cleanerLock"
/>

<bean id="cleanerLock"
  class="org.jasig.cas.ticket.registry.support.JdbcLockingStrategy"
  p:uniqueId="${host.name}"
  p:platform="${ticket.cleaner.database.platform}"
  p:applicationId="cas-ticket-registry-cleaner"
  p:dataSource-ref="dataSource"
/>

<bean id="ticketRegistryCleanerJobDetail"
  class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"
  p:targetObject-ref="ticketRegistryCleaner"
  p:targetMethod="clean"
/>

<bean id="periodicTicketRegistryCleanerTrigger"
  class="org.springframework.scheduling.quartz.SimpleTriggerBean"
  p:jobDetail-ref="ticketRegistryCleanerJobDetail"
  p:startDelay="20000"
  p:repeatInterval="1800000"
/>

...

Now that the maven pom file and the CAS configuration files have been modified we're ready to build the deployable CAS war. Once again, open a console window and navigate to C:\CAS_SSO\local-cas and execute the maven command to build the project:

No Format

mvn clean package -DskipTests=true

...

Browse to http://localhost:8080/cas/services/manage.html, the Services Management application. Once you login you'll see a warning to add the Management service itself as the first service. Click the 'Add New Service' button and add a service with the following URL:

Code Block

http://localhost:8080/cas/services/j_acegi_cas_security_check

...

Access the test application entry points using these URLs:

Code Block

https://<protected.server.com>:8443/TestApp1/index.jsp
https://<protected.server.com>:8443/TestApp2/index.jsp

...

The URL patterns determine which pages are protected by CAS. The patterns should match what was entered into Services Management. Note that the URL pattern for the single sign-out filter matches all pages in the app.

No Format

<filter>
  <filter-name>CAS Single Sign Out Filter</filter-name>
  <filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class>
</filter>
<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://[CAS Server FQDN]:8443/cas/login</param-value>
  </init-param>
  <init-param>
    <param-name>serverName</param-name>
    <param-value>https://[Client Server FQDN]:8443</param-value>
  </init-param>
</filter>
<filter>
  <filter-name>CAS Validation Filter</filter-name>
  <filter-class>org.jasig.cas.client.validation.Saml11TicketValidationFilter</filter-class>
  <init-param>
    <param-name>casServerUrlPrefix</param-name>
    <param-value>https://[CAS Server FQDN]:8443/cas</param-value>
  </init-param>
  <init-param>
    <param-name>serverName</param-name>
    <param-value>https://[Client Server FQDN]:8443</param-value>
  </init-param>
  <init-param>
    <param-name>redirectAfterValidation</param-name>
    <param-value>true</param-value>
  </init-param>
  <init-param>
    <param-name>tolerance</param-name>
    <param-value>5000</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>

<filter-mapping>
  <filter-name>CAS Single Sign Out Filter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
  <filter-name>CAS Authentication Filter</filter-name>
  <url-pattern>/sso/*</url-pattern>
</filter-mapping>
<filter-mapping>
  <filter-name>CAS Validation Filter</filter-name>
  <url-pattern>/sso/*</url-pattern>
</filter-mapping>
<filter-mapping>
  <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
  <url-pattern>/sso/*</url-pattern>
</filter-mapping>
<filter-mapping>
  <filter-name>CAS Assertion Thread Local Filter</filter-name>
  <url-pattern>/sso/*</url-pattern>
</filter-mapping>

...the rest of the application...

...

The user's LDAP attributes are added to the Principal. They can be accessed in a servlet or JSP as shown below.

Code Block

// get the user's credentials
Principal p = request.getUserPrincipal();
String userLoginName = request.getRemoteUser(); // or p.getName()

// get the released attributes, e.g., LDAP group membership - earlier mapped to 'Groups'
AttributePrincipal principal = (AttributePrincipal)p;
Map attributes = principal.getAttributes();
Object value = attributes.get("Groups");
if (value instanceof String) {
   // memberOf contained one group name
} else if (value instanceof List) {
   // memberOf contained multiple group names
}

...

The single sign-out button invokes the LogoutServlet, which contains the method shown below. It redirects to the CAS logout URL, which causes CAS to issue logout commands to all apps that are participating in the login session.

Code Block

public void doSingleSignOut(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
  response.sendRedirect("https://<cas.server.com>:8443/cas/logout");
}