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. |
- #OverviewOverview
- #Prerequisites Prerequisites
- #Use Use the Maven Overlay Method to Build the SSO Server
- #Deploy Deploy the Server
- #Deploy Deploy the Protected Applications
- CAS-ifying the Client Applications
...
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 | ||
---|---|---|
| ||
<?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 protected pages have buttons for both application logout and single sign-out. Application logout invalidates the current session, but if you navigate . Note that if you still have a valid CAS SSO login session, i.e., you're in another protected application in a different browser tab, then navigating to the protected page again will log you 'll be logged right back in because you still have a valid CAS SSO login session.
Single sign-out, on the other hand, will require you to login again for any action that accesses a protected page, even refreshing the page.
CAS-ifying the Client Applications
...
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");
}
|