Monday, September 23, 2013

SSO with CAS, Part 2: The Service Provider

In my last post, I explained how to set up an Identity Provider with CAS.  In this example, we'll configure our first Service Provider that will authenticate against it.

1. Set up the Service Provider app

With our CAS server configured, we now need to configure our clients to use it.  These clients are called Service Providers (SP) in SSO parlance, because depend on the Identity Provider (IdP) to authenticate the end user before providing the service.  Although possible to run both the IdP and SPs from the same server, for the most accurate simulation of SSO, the Service Providers should be installed on a completely different box.  For my example, I will assume we have a new machine with a fresh install of Tomcat.

For our first client (I'll call it cas-app1), I will just gate access to a simple Hello World app (just pretend it provides a valuable service).  Tomcat actually provides a Hello World app for us already in it's tomcat7-examples package, so we'll just use that.  This examples package is included by default in the direct download from the Apache Tomcat website, or on Ubuntu you can install it using a tool like apt-get.

(*Note for Ubuntu: apt-get puts the Tomcat webapps folder in /var/lib/tomcat7, but installs tomcat7-examples in /usr/share/tomcat7-examples)

2. Add CAS client JARs to Tomcat's default examples app

As with the CAS server, we'll need to download the CAS client jars (latest as of this writing: 3.2.1), and build the binaries using Maven.  We will plug these into our SP Hello World app to force it to talk to the IdP.
  mvn -Dmaven.test.skip=true package install  

Once the build finishes, there are three subpackages we're interested in: cas-client-core, cas-client-integration-tomcat-common, cas-client-integration-tomcat-v7, located in the modules subdirectory.

We simply copy those jar files into the tomcat7-examples/WEB-INF/lib directory.  The CAS client also requires us to use Apache Commons Logging, so we download that library and drop it into tomcat7-examples/WEB-INF/lib as well.

3. Create a self-signed certificate, and turn on SSL in Tomcat.

This step is identical to steps #2 and #3 for the CAS server.  Again, this is required because CAS requires all parties to communicate using SSL.

4. Add CAS certificate to trusted keystore

In step #5 of the CAS server setup, we exported the certificate for our private key for use in our SPs.  But because this is a self-signed certificate the client SPs consider it untrusted, and will therefore not actually accept data signed with it.  When a browser encounters a self-signed cert it warns the user and then allows him to proceed at his own risk.  But our SSO data is interpreted server-side rather than in the browser, so we need to add it to a trusted store.

There are a couple of ways to do this.  The easiest is to simply add it to Java's own trusted CA file.  Again, we use keytool:
  keytool -import -file cas.crt -alias cas -keystore $JAVA_HOME/jre/lib/security/cacerts  

Answer "yes" to the question of whether the certificate should be trusted.  Note: if multiple versions of Java are installed, the certificate must be imported into the version Tomcat is running with.

If modifying the Java CA file is not an option, an alternative is to import it into its own keystore and tell Tomcat where to find it.
  keytool -import -file cas.crt -alias cas -keystore /path/to/special/keystore  

Then modify the connector as defined in the Tomcat connector documentation:
   <Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true"  
        maxThreads="150" scheme="https" secure="true"  
        clientAuth="false" sslProtocol="TLS"  
        truststoreFile="/path/to/special/keystore"  
        truststorePass="SPECIAL_KEYSTORE_PASSWORD"  
        />  

5. Add servlet filters to SP app

The last thing we need to do is tell our SP app how to talk to the IdP.  This is done by configuring servlet filters in tomcat7-examples/WEB-INF/web.xml, as shown below.  These filters will intercept incoming calls to our SP, redirect to CAS for authentication if necessary, and redirect the user back to the page they wanted.

Note: replace the example URLs with your own IdP and SP URLs.
   <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.example.com:8443/cas/login</param-value>  
    </init-param>  
    <init-param>  
     <param-name>serverName</param-name>  
     <param-value>https://cas-app1.example.com</param-value>  
    </init-param>  
   </filter>  
   
   <filter>  
    <filter-name>CAS Validation Filter</filter-name>  
    <filter-class>org.jasig.cas.client.validation.Cas10TicketValidationFilter</filter-class>  
    <init-param>  
     <param-name>casServerUrlPrefix</param-name>  
     <param-value>https://cas.example.com:8443/cas</param-value>  
    </init-param>  
    <init-param>  
     <param-name>serverName</param-name>  
     <param-value>https://cas-app1.example.com</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 Authentication Filter</filter-name>  
    <url-pattern>/*</url-pattern>  
   </filter-mapping>  
   
   <filter-mapping>  
    <filter-name>CAS Validation Filter</filter-name>  
    <url-pattern>/*</url-pattern>  
   </filter-mapping>  
   
   <filter-mapping>  
    <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>  
    <url-pattern>/*</url-pattern>  
   </filter-mapping>  
   
   <filter-mapping>  
    <filter-name>CAS Assertion Thread Local Filter</filter-name>  
    <url-pattern>/*</url-pattern>  
   </filter-mapping>  

6. Test it

The last thing we do is test our app by navigating a browser to our SP: https://cas-app1.example.com/examples/servlets/servlet/HelloWorldExample.

If we've successfully configured everything, we should be redirected to our IdP server where it prompts for a login.  After authenticating (remember any identical user/password combo will work), we should then be immediately routed back to the Hello World app.

If we're unsuccessful, our SP will give us a 404.  In order to see the real problem, we'll need to check the Tomcat server logs.

Conclusion

We now have a working Identity Provider, and have validated it via a very basic Service Provider.  But for us to consider this true SSO, we really need additional SPs and verify that when we log into one, we are also logged into the others.

To do this, we could set up multiple clones of this Hello World SP, but that wouldn't be very interesting.  In my next post, we will configure yet another Hello World SP, but this time integrate with some real-world frameworks we may encounter in the wild, such as Spring and Shiro.