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.

Friday, September 20, 2013

SSO with CAS, Part 1: The Identity Provider



I recently had the opportunity to experiment with SSO using the CAS project (Central Authentication Service).  Unfortunately while the documentation and examples on the CAS website are sometimes helpful, they are also often lacking or out-of-date.  In this and subsequent posts, I'll relate my own experience setting up these servers.

For the purposes of these examples, I provisioned virtual machines on Amazon Web Services EC2 configured with Ubuntu 12, Tomcat 7, and Java 6.  In principle, the steps should not change much with a different OS or servlet container.  However, I assume in this post that you know how to install these things yourself on whatever platform you choose.

Also, CAS requires SSL.  For testing I did not want to purchase a real certificate, so I generated and installed self-signed certificates instead, which creates extra steps that you would not need in a production system.  Obviously if you may skip those steps if you have a real certificate.

1. Build the CAS war

In order to set up SSO we'll first need an Identity Provider (IdP).  In our case, a simple CAS server will do (latest as of this writing: 3.5.2).  The CAS war needs to be build manually, so we download the source and compile it ourselves (which requires Maven).

  mvn -Dmaven.test.skip=true package install  

This will generate a modules subdirectory where the cas-server-webapp.war is located.  Copy that file into the Tomcat webapps folder, and rename it to cas.war.

Note: there is also a cas-server-uber-webapp.war file, which packages all the CAS support jars into the war by default.  Either war will work, but I prefer a leaner webapp to start with, and then add modules as I need them, so I chose the former.

2. Create a self-signed certificate

As mentioned, CAS requires SSL.  So we need to generate a self-signed certificate to get started.  The JDK comes with a very handy tool called keytool that can help us.

  keytool -genkey -alias tomcat -keyalg RSA -keystore /usr/share/tomcat7/.keystore  

This generates a new key pair and adds it to the keystore in /usr/share/tomcat7, which is the home directory for the user tomcat.  The location of the keystore is important: by default, Tomcat will look for certificates in the home directory of the user it's running as.  On Ubuntu, Tomcat runs as the user tomcat, and so we put the keystore in its home directory (note: keytool will create the keystore if it doesn't exist).

As part of creating the keystore and the certificate, the keytool will ask for a password.  The default password used by Tomcat is "changeit" (all lower case), and I recommend following that convention even though it's possible to choose a different password.  Choosing a custom password can lead to some unexpected behavior unless you configure Tomcat correctly.

3. Turn SSL on in Tomcat

The final step to activating SSL is to tell Tomcat to use it.  This is as simple as opening Tomcat's conf/server.xml and uncommenting the appropriate line:

   <Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true"  
        maxThreads="150" scheme="https" secure="true"  
        clientAuth="false" sslProtocol="TLS"  
        />  

This assumes a lot of defaults.  There are many potential things to customize, and reviewing them is outside the scope of this blog post.  See the Tomcat SSL documentation if you need to do more than this.

4. (Optional) Fix CAS log location

The default logs directory for CAS is the local web directory, and this causes permission problems on Linux.  In order to work around it, we can modify the CAS webapp's log4j settings (located at /WEB-INF/classes/log4j.xml), and point cas.log and perfStats.log to a writable directory.

If you don't do this, you may see log write errors in the Tomcat logs, but CAS will still continue to function as expected.

5. Export CAS certificate

Because we're using a self-signed certificate, we need to export that cert and take it with us to any of the client Service Providers that will use SSO.  That certificate will need to be installed as a trusted source in the certificate chain on those boxes.  We'll get to that in a later post, but for now, export the certificate like so:

  keytool -export -alias tomcat -file cas.crt -keystore /usr/share/tomcat7/.keystore  

Hang on to this file for later.

6. Test it (cas.example.com)

The last thing we do is test our server by navigating a browser to the IdP main URL: https://cas.example.com:8443/cas.  We should be challenged with a login screen.

The default CAS server configuration uses an authentication scheme called SimpleTestUsernamePasswordAuthenticationHandler, which accepts and authenticates any user/password combination that is identical.  That is, admin/admin, jacksmith/jacksmith, etc. will all authenticate.  In the Tomcat logs, CAS warns us this authentication scheme ought never to be used in a production environment, but for now it's okay.  We'll cover user provisioning in a subsequent post.

Conclusion

We now have a fully functional IdP that can respond to CAS requests from any Service Provider (SP) trying to authenticate a user.  In my next post, we'll set up a very simple SP to do just this.