Monday, October 6, 2014

How to customize JA-SIG CAS authentication process

Some people see Central Authentication Service (CAS) as a black box that magically tells you if you are authenticated against certain accreditation or not... Configuration is dead simple. All you have to do is to specify chain of authentication mechanisms in /src/main/webapp/WEB-INF/deployerConfigContext.xml file. CAS would try your login credentials against each one and eventually would either fail or pass.

Today, I would like to lift some of this mystery out and tell you how to customize parts of JA-SIG CAS to fit your requirements.

Here is the scenario, you are trying to authenticate via CAC card (X509 certificate) and need additional validation after user keys in his PIN. Let's say that we need to verify that the user is a part of certain organization that is listed in his certificate.

Step 1:
Create new Maven Web Application project

Step 2:
Add CAS dependencies to pom.xml file

        <!-- Main guts of CAS -->
         <dependency>
            <groupId>org.jasig.cas</groupId>
            <artifactId>cas-server-webapp</artifactId>
            <version>${cas.version}</version>
            <type>war</type>
            <scope>runtime</scope>
        </dependency>

        <!-- Needed to interact with X509 certificates -->
        <dependency>
            <groupId>org.jasig.cas</groupId>     
            <artifactId>cas-server-support-x509</artifactId>     
            <version>${cas.version}</version>
        </dependency>

Step 3:
Clean and build the project. It would download all the dependencies for your project.

Step 4: 
Add following line under 
<bean id="authenticationManager" class="org.jasig.cas.authentication.AuthenticationManagerImpl"> 
    <property name="credentialsToPrincipalResolvers">
         <list>

in your /src/main/webapp/WEB-INF/deployerConfigContext.xml configuration file.

<bean  class=  "org.jasig.cas.adaptors.x509.authentication.principal.X509CertificateCredentialsToIdentifierPrincipalResolver">
</bean>

Per documentation, "This is the List of CredentialToPrincipalResolvers that identify what Principal is trying to authenticate. The AuthenticationManagerImpl considers them in order, finding a CredentialToPrincipalResolver which supports the presented credentials."

This would basically tell our CAS to invoke X509CertificateCredentialsToIdentifierPrincipalResolver

Step 5:
Add following line under
<bean id="authenticationManager" class="org.jasig.cas.authentication.AuthenticationManagerImpl"> 
    <property name="authenticationHandlers">
         <list>

in the same configuration file.

    <bean class="org.jasig.cas.adaptors.x509.authentication.handler.support.X509CredentialsAuthenticationHandler">
        <property name="trustedIssuerDnPattern" value=".*, OU=<replace it>, O=<replace it>, C=<replace it>" />
        <property name="subjectDnPattern"  value=".*, OU=<replace it>, O=<replace it>, C=<replace it>" />
    </bean>

Per documentation: "Whereas CredentialsToPrincipalResolvers identify who it is some Credentials might authenticate, AuthenticationHandlers actually authenticate credentials.  Here we declare the AuthenticationHandlers that authenticate the Principals that the CredentialsToPrincipalResolvers identified.  CAS will try these handlers in turn until it finds one that both supports the Credentials presented and succeeds in authenticating."

Step 6:
Let's review... We have a new project that has all CAS dependencies in it and is configured to authenticate against CAC card by using org.jasig.cas.adaptors.x509.authentication.handler.support.X509CredentialsAuthenticationHandler

Step 7.
All JA-SIG CAS is public! So don't be afraid to review it! Get the latest release via git or download specific release that you perhaps specified in the pom file of our project.
In our case we are interested in

  • org.jasig.cas.adaptors.x509.authentication.principal.X509CertificateCredentialsToIdentifierPrincipalResolver
  • org.jasig.cas.adaptors.x509.authentication.handler.support.X509CredentialsAuthenticationHandler

Step 8.
X509CertificateCredentialsToIdentifierPrincipalResolver - is pretty basic and does not contain an actual logic of authentication. Remember? "Whereas CredentialsToPrincipalResolvers identify who it is some Credentials might authenticate, AuthenticationHandlers actually authenticate credentials."
So let's look at X509CredentialsAuthenticationHandler

Step 9.
X509CredentialsAuthenticationHandler contains
protected final boolean doAuthentication(final Credentials credentials)
method that performs actual authentication and decides if user will be permitted to access the system or not. Here is an extract of the code that is self explanatory:
     
       if (valid && hasTrustedIssuer && clientCert != null) {
      x509Credentials.setCertificate(clientCert);
      this.log.info("Successfully authenticated " + credentials);
      return true;
       }
       this.log.info("Failed to authenticate " + credentials);
       return false;

Step 10.
So what if you were to asked to change the default behavior of X509CredentialsAuthenticationHandler in terms what it does to authenticate or if you were asked to extend it? Perhaps upon successful X509 authentication, you were required to check if specific user was a part of some group inside of LDAP? What to do?

Step 11.
In your Source Packages, create a new package that would be EXACTLY the same as X509CredentialsAuthenticationHandler so let's create org.jasig.cas.adaptors.x509.authentication.handler.support package.
Now, you need to add X509CredentialsAuthenticationHandler.java to this package, or you can rename it to something else to distinguish it from the "original" implementation. If you do decide to rename it, please make sure to update its name in /src/main/webapp/WEB-INF/deployerConfigContext.xml (Step 5).

Step 12.
Copy the source code of the X509CredentialsAuthenticationHandler and paste it into your new java class. Alter doAuthentication method to contact external LDAP server with extracted IssuerDN (or whatever) to check for specific group membership or any other requirement you might have.

Step 13.
Alter the last step where doAuthentication method returns authentication pass or fail flag (See step 9) to whatever you see fit your requirements.

Step 14.
Clean and Build again for changes to take effect.

Step 15.
Deploy generated cas.war file onto Tomcat or any other server, and try it out!
Please note, that to be able to generate cas.war after the build, you need to add

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-war-plugin</artifactId>
                <configuration>
                    <warName>cas</warName>
                </configuration>
            </plugin>
        </plugins>
    </build>

to your pom.xml file.




Hopefully this was useful to you and you are not feel more comfortable working and modifying internals of CAS. Like always feel free to reach out to me with questions.