Monday, January 09, 2012

SSO with LDAP Spring and CAS

This is a step by step documentation on how to setup a CAS Server and use Single Sign On (SSO) plus Single Sign Off from Spring client applications using LDAP to store authentication and authorization information. CAS support can be enabled or disabled commenting sections of the code.

Our Use Case for this exercise is to have a centralized server that handles user login and logout operations allowing the user to sign in just once and enjoy multiple applications thanks to that centralization.

We then achieve Federation because thanks to SSO via CAS we obtain Authentication while we pull the attributes from LDAP to achieve Authorization. At the end the user is trusted after the first login and the attributes can be obtained from any application.

Before we review Server and Client configuration be sure you understand CAS needs SSL so in order to correctly follow this post you will need to have the CAS Server and two Spring client applications serving on https. SSL needs an IP per domain and if you are running OSX by default you have only one loopback IP (127.0.0.1). This means you will need to add other two:
sudo ifconfig lo0 alias 127.0.0.2 up
sudo ifconfig lo0 alias 127.0.0.3 up

Then set in /etc/hosts a domain per loopback IP:
$ vi /etc/hosts
127.0.0.2   bhubdev.nestorurquiza.com
127.0.0.2   portaldev.nestorurquiza.com
127.0.0.3   casdev.nestorurquiza.com

You should be able to get the certificate from each URL and assert they are correct, for example:
$ echo | openssl s_client -connect bhubdev.nestorurquiza.com:443 2>/dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > ~/Downloads/bhubdev.nestorurquiza.com.cer
$ echo | openssl s_client -connect portaldev.nestorurquiza.com:443 2>/dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > ~/Downloads/portaldev.nestorurquiza.com.cer
$ echo | openssl s_client -connect casdev.nestorurquiza.com:443 2>/dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > ~/Downloads/casldev.nestorurquiza.com.cer
$ openssl x509 -text -in ~/Downloads/bhubdev.nestorurquiza.com.cer
$ openssl x509 -text -in ~/Downloads/portaldev.nestorurquiza.com.cer
$ openssl x509 -text -in ~/Downloads/casdev.nestorurquiza.com.cer

Once you are sure the certificates coming from the different URLs are correct (they belong to the specific domain) then you should add them to your keystore:
$ sudo keytool -delete -alias bhubdev.nestorurquiza.com -keystore  /Library/Java/Home/lib/security/cacerts
$ sudo keytool -delete -alias portaldev.nestorurquiza.com -keystore  /Library/Java/Home/lib/security/cacerts
$ sudo keytool -delete -alias casdev.nestorurquiza.com -keystore  /Library/Java/Home/lib/security/cacerts
$ sudo keytool -import -alias bhubdev.nestorurquiza.com -keystore  /Library/Java/Home/lib/security/cacerts -file ~/Downloads/bhubdev.nestorurquiza.com.cer
$ sudo keytool -import -alias portaldev.nestorurquiza.com -keystore  /Library/Java/Home/lib/security/cacerts -file ~/Downloads/portaldev.nestorurquiza.com.cer
$ sudo keytool -import -alias casdev.nestorurquiza.com -keystore  /Library/Java/Home/lib/security/cacerts -file ~/Downloads/casdev.nestorurquiza.com.cer

Of course after you do all the above tomcat must be restarted and if you use Apache for virtual hosting like I explain below you owe to restart it as well.

CAS Server installation and configuration

  1. Download CAS Server from http://www.jasig.org/cas and get cas.war deployed in your server. At this point http://localhost:8080/cas should prompt for credentials. I commonly use apache in front of tomcat so below are the steps using virtual hosts as I have documented before:
    $ mkdir /Users/nestor/Downloads/apache-tomcat-7.0.22/cas
    $ mkdir /Users/nestor/Downloads/apache-tomcat-7.0.22/conf/Catalina/casdev.nestorurquiza.com
    $ cp /Users/nestor/Downloads/cas-server-3.4.11/modules/cas-server-webapp-3.4.11.war /Users/nestor/Downloads/apache-tomcat-7.0.22/cas/ROOT.war
    
  2. Prepare CAS to use LDAP authentication. This is about copying two files to the WEB-INF/lib directory and editing a configuration file in that same directory:
    $ cp /Users/nestor/Downloads/cas-server-3.4.11/modules/cas-server-support-ldap-3.4.11.jar /Users/nestor/Downloads/apache-tomcat-7.0.22/cas/ROOT/WEB-INF/lib 
    $ cp /Users/nestor/Downloads/spring-ldap-1.3.0.RELEASE/dist/spring-ldap-1.3.0.RELEASE-all.jar /Users/nestor/Downloads/apache-tomcat-7.0.22/cas/ROOT/WEB-INF/lib
    $ vi /Users/nestor/Downloads/apache-tomcat-7.0.22/cas/ROOT/WEB-INF/deployerConfigContext.xml
    …
     
     ...
      
       
        
        
        
        
        
        
          
          
          
        
       
      
    
  3. Get spring-security source code





    ldaps://ldapint.nestorurquiza.com:10636













  4. While at this point you should be able to login from the CAS landing page using your LDAP credentials CAS needs to run from an SSL URL in order to be used as Single Sign On (SSO). I use Apache and mod-jk with Tomcat so my SSL configuration is achieved on the Apache side. Here are the steps to generate the self signed certificate and configure Apache virtual host in OSX to serve the CAS service:
    $ cd ~/Downloads/
    $ sudo mkdir /private/etc/apache2/certs/
    $ export DOMAIN=casdev.nestorurquiza.com
    $ openssl genrsa -des3 -out ${DOMAIN}.key 1024
    $ openssl req -new -key ${DOMAIN}.key -out  ${DOMAIN}.csr
    $ openssl x509 -req -days 365 -in ${DOMAIN}.csr -signkey ${DOMAIN}.key -out ${DOMAIN}.crt
    $ openssl rsa -in ${DOMAIN}.key -out ${DOMAIN}.key
    $ sudo cp ${DOMAIN}.key /private/etc/apache2/certs/
    $ sudo cp ${DOMAIN}.crt /private/etc/apache2/certs/
    $ sudo vi /private/etc/apache2/extra/httpd-vhosts-ssl.conf
    Listen 443
    AddType application/x-x509-ca-cert .crt
    AddType application/x-pkcs7-crl    .crl
    SSLPassPhraseDialog  builtin
    SSLSessionCache        "shmcb:/private/var/run/ssl_scache(512000)"
    SSLSessionCacheTimeout  300
    SSLMutex  "file:/private/var/run/ssl_mutex"
    
    
    DocumentRoot "/usr/docs/casdev.nestorurquiza.com"
    ServerName casdev.nestorurquiza.com:443
    ServerAdmin nurquiza@nestorurquiza.com
    ErrorLog "/private/var/log/apache2/casdev.nestorurquiza.com.log"
    TransferLog "/private/var/log/apache2/casdev.nestorurquiza.com.log"
    SSLEngine on
    SSLCipherSuite ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP:+eNULL
    SSLCertificateFile "/private/etc/apache2/certs/casdev.nestorurquiza.com.crt"
    SSLCertificateKeyFile "/private/etc/apache2/certs/casdev.nestorurquiza.com.key"
    
        Options +FollowSymLinks
        AllowOverride All
        Order deny,allow
        Allow from all
    
    JkMount "/*" nestorurquiza-app1
    
    $ sudo vi /etc/apache2/httpd.conf
    ...
    LoadModule ssl_module libexec/apache2/mod_ssl.so
    ...
    Include /private/etc/apache2/extra/httpd-vhosts-ssl.conf
    …
    $ sudo apachectl configtest
    $ sudo apachectl graceful
    
  5. Logout and login from SSL. Note that you do not get any alerts stating SSO won't work:
    https://casdev.nestorurquiza.com/logout
    https://casdev.nestorurquiza.com
    
  6. Change the look and feel for the CAS login page editing the file /WEB-INF/view/jsp/default/ui/casLogoutView.jsp and dependencies like the logo from /css/cas.css #header background url and #header h1#app-name background color
  7. Further customize the login page for example as I needed to provide a forgot password functionality I came up with the below implementation in casLogoutView.jsp:
      
      
    <a href="<c:out value="${schema}://${domain}/forgotPassword"/>">Forgot Password</a>
  8. Import the client website certificate in the CAS server for example:
    $ echo | openssl s_client -connect bhubdev.nestorurquiza.com:443 2>/dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > ~/Downloads/bhubdev.nestorurquiza.com.cer
    $ sudo keytool -import -alias bhubdev.nestorurquiza.com -keystore  /Library/Java/Home/lib/security/cacerts -file ~/Downloads/bhubdev.nestorurquiza.com.cer
    



Spring CAS Client

  1. Import the CAS certificate in the client
    $ echo | openssl s_client -connect casdev.nestorurquiza.com:443 2>/dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > ~/Downloads/casdev.nestorurquiza.com.cer
    $ sudo keytool -import -alias casdev.nestorurquiza.com -keystore  /Library/Java/Home/lib/security/cacerts -file ~/Downloads/casdev.nestorurquiza.com.cer
    
  2. Declare some properties in the classpath of the client application:
    ...
    #
    # CAS
    #
    cas.loginURL=https://casdev.nestorurquiza.com/login
    cas.homeURL=https://casdev.nestorurquiza.com
    cas.serviceURL=https://bhubdev.nestorurquiza.com/j_spring_cas_security_check
    cas.logoutURL=https://casdev.nestorurquiza.com/logout?service=https%3A%2F%2Fbhubdev.nestorurquiza.com%2Flogout
    …
    
  3. Then configure the client to perform the Single Sign On and Single Sign Off through CAS. Here is the Spring security configuration for such a use case:
    ...
        
        
        
        
        
            
            
            
            
     
            
            
            
            
                       
        
        
            
            
        
        
        
          
        
        
        
          
          
        
        
        
          
        
        
        
          
          
        
        
        
            
            
            
              
                
                
            
            
        
    
        
    
  4. A simple JSP View for the client application Logout Controller
    <%@ include file="/WEB-INF/jsp/includes.jsp" %>
    <%@ include file="/WEB-INF/jsp/header.jsp" %>
    
    

    <%@ include file="/WEB-INF/jsp/footer.jsp" %>

  5. The LogoutController
    package com.nestorurquiza.web;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import org.springframework.security.core.context.SecurityContextHolder;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.servlet.ModelAndView;
    
    @Controller
    public class LogoutController extends RootController {
    
        @RequestMapping("/logout")
        public ModelAndView welcomeHandler(HttpServletRequest request,
                HttpServletResponse response) {
            SecurityContextHolder.getContext().setAuthentication(null);
            
            ...
            
            return getModelAndView(ctx, "logout", null);
        }
    }
    
  6. The logout link in JSP:
    
                            
                                
                            
                           
                                
    


  7. At least one dependency is needed in the client application:
    
                org.springframework.security
                spring-security-cas-client
                ${spring-security.version}
                compile
            
    


  8. At this point hitting any client application URL (for example https://bhubdev.nestorurquiza.com/client/home) will redirect the user to the CAS login URL passing the service parameter which contains the encoded servciceURL (https://casdev.nestorurquiza.com/login?service=https%3A%2F%2Fbhubdev.nestorurquiza.com%2Fj_spring_cas_security_check). When the user provides valid credentials CAS will redirect her back to the j_spring_cas_security_check client resource and Spring will take care to pull from the session the originally requested URL. When the user clicks on the logout URL (https://casdev.nestorurquiza.com/logout?service=https%3A%2F%2Fbhubdev.nestorurquiza.com%2Flogout) the CAS server will invalidate the security token and the user will return to a URL where a custom Logout Controller invalidates the Spring Security Context. At this point any attempt to access a protected resource will result in a redirect to the CAS login page as explained.


  9. Probably you will need in your custom code to do extra logic with the saved request (the original requested URL). In Spring 3.0.3 at least you use the below snippet to gain access to it:
    SavedRequest savedRequest = new DefaultSavedRequest(request, new PortResolverImpl());
    if( savedRequest != null ) {
      String redirectUrl = savedRequest.getRedirectUrl();
    


  10. Clearly you can proceed with the final step from the Server and the steps from the Client to configure extra applications (CAS Services). When you logout from one application or directly from CAS interface you will be logged out from all applications that are integrated with CAS (Single Sign Out or Single Sign Off functionality)


Other than a little issue I reported here the whole integration went smooth. CAS is well supported in Spring and both Single Sign On and Single Sign Off will work out of the box.

By default CAS is shipped with a 4 hours expiration ticket expiration policy. This can be configured as explained in the documentation https://wiki.jasig.org/display/CASUM/Ticket+Expiration+Policy

2 comments:

ariel said...

Can you provide us with the source code for this sample? Thxs

Nestor Urquiza said...

@ariel Sorry no complete source code can be provided however any Spring project should be able to be adapted to work with CAS and LDAP in order to provide SSO. Perhaps you can open a project in github cloning a couple of the Spring demo projects to provide SSO integration between them.

Followers