CAS with Spring Integration


                                                                   Sichern is Secure

Spring has support for CAS(Central Authentication Service) which provides Single Sign On Authentication mechanism.

Though there are various sites available for the above mentioned integration, but while doing the setup I could not find
all the configuration at the same place.

This blog will consists of all the nitty gritty and the bits and pieces I have gone through while doing the setUP.
Any furthur suggestions will be taken wholeheartedly.

I have integrated Spring 3 with CAS in jBoss AS 7, though in jBosss AS 7 the configuration is almost alike JbOSS 5.1 .

Please note all the below mentioned steps are based on the issues which I have faced (during initial setup and configuration) and the steps taken for making the CAS server and application up and running with minimal configuration, this may not be suitable for integrating for Production environment or for for
advanced uses.

Steps:-
a)     First i have downloaded the CAS server from "http://www.jasig.org/cas/download"
b)    Unzip the file and extract the war file "cas-server-webapp-3.5.2.war" from the location "cas-server-3.5.2-release\cas-server-3.5.2\modules" as present in the
    unzipped file
c)    rename the war file to cas.war and explode it(unzip it). Deploying war in exploded format gives us the added advantage of changing the contents
    within the exploded war and deploying at the same time without archiving it.
               
d)    For deployment in jBoss AS 7
                i)    Put the exploded war in "jboss-as-7.1.1.Final\standalone\deployments" folder along with an empty "cas.war.dodeploy" file
                    (Since the  name of our application is (cas.war)
                ii)    Restart the server
f)    After restarting the sever I have faced some issues for makin the CAS Server up and running. This can be solved with the following approaches.
               
                i)    Prob: Appropiate connection provider not found.
                    Soln: Add     "<jta-data-source>java:jboss/datasources/ExampleDS</jta-data-source>" (JNDI name of the datasource)
                                                                   
                                                                    OR
                                "<properties>
                                                <property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect"/>
                                </properties>"
                               
                                in the "cas.war\WEB-INF\classes\META-INF\Persistence.xml" file in the deployment folder of the server.
               
                ii)    Prob: Appropiate log4j.xml file not found.
                    Soln: Provide the absolute path of the log4j.xml file in the "cas.war\WEB-INF\spring-configuration\log4jConfiguration.xml"  file.
                   
g)    Configure Jboss for SSL as the CAS server should be accessed over https.
    Steps for  configuration:
   
                i)    create a keystore along the the public cerificate with the following command.
                    "keytool -genkey -keyalg RSA -alias alias -keystore "<<Full path of the .keystore file to be created>>" -storepass password -validity 360 -keysize 2048"
               
                ii) Configure the configuration file of the Server to use the above created keystore for SSL .
                           
                            *)    For jBoss AS7- Change in "jboss-as-7.1.1.Final\standalone\configuration\standalone.xml" file to include the following:
                                "<connector name="https" protocol="HTTP/1.1" scheme="https" socket-binding="https" secure="true">
                                        <ssl name="https" password="password" certificate-key-file=""<<Full path of the .keystore file  created in the previous step>>"/>
                                </connector>"
                               
The Architectural  and Functional Overview can be consulted out from Spring Securty 3 by Peter Mularien.
I have followed the steps mentioned in the above book for basic configuration, If there is any deviation from the configuration defined in the book for setting up the basic Spring Security with CAS Integration then only I will mention it (to avoid redundancy), otherwise everything will be according to the defined configuration in the book.


All the custom configurations corresponding to the bean definition will be provided herewith.

For logging in my application I have used log4j2 API.
The configuration for log4j is straightforward.

The configuration details are to be kept in log4j2.xml file, which i have kept in WEB-INF/classes folder in my application war file, and the corresponding to jar files i) log4j-api-2.0-beta4.jar
                                                                                      ii) log4j-core-2.0-beta4.jar
in WEB-INF/lib folder i.e. in classpath.

The configuration for logr4j2 is as follows (I have used basic configuration here).


<?xml version="1.0" encoding="UTF-8"?>

<configuration status="warn" name="SpringREST">

<properties>
<property name="logFileLocation"><<Location where the log file would be created>></property>
</properties>

<appenders>

<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>

<File name="MyFile" fileName="${logFileLocation}/WSFileApp.log">
<PatternLayout>
<pattern>%d %p %c %C %M [%t] --: %m%n%exception{full}</pattern>
</PatternLayout>
</File>

<RollingFile name="RollingFile" fileName="${logFileLocation}/<<File name>>.log"
filePattern="${logFileLocation}/$${date:yyyy-MM}/WSRollFileApp-%d{yyyy-MM-dd-HH}-%i.log.gz">

<PatternLayout>
<pattern>%d %p %c %M --: %m%n%exception{full}</pattern>
</PatternLayout>
<Policies>
<SizeBasedTriggeringPolicy size="2 MB"/>
</Policies>
</RollingFile>

</appenders>

<loggers>

<logger name="<<Context to be logged>>" level="debug" additivity="false">
<appender-ref ref="RollingFile"/>
</logger>

<root level="error">
<appender-ref ref="Console"/>
</root>
</loggers>

</configuration>

The configuration descrition of log4j2.xml can be easily consulted out from http://logging.apache.org/log4j/2.x/manual/.

I preferred log4j2 (though only beta version is available now) because of its varied advantages as depicted by the apache official site.

Since I am configuring my own CAS AuthenticationHandlers,CredentialToPrincipalResolver  and other components all the CAS level custom classes logging I have configured in Jboss7.1 standalone.xml, in the following section.

(The highlighted section points out the section in standalone.xml)

I have configured an appender/handler and a logger.

 <profile>
        <subsystem xmlns="urn:jboss:domain:logging:1.1">

        <!-- appender/handler -->
        <periodic-rotating-file-handler name="CASAPPLICATION">
                <formatter>
                    <pattern-formatter pattern="%d{HH:mm:ss,SSS} %-5p [%c] (%t) %s%E%n"/>
                </formatter>
                <file path="<< Log File Location >>"/>
                <suffix value=".yyyy-MM-dd"/>
                <append value="true"/>
            </periodic-rotating-file-handler>

           <!-- logger-->
           <logger category="com.cas">
                <level name="DEBUG"/>
                <handlers>
                    <handler name="CASAPPLICATION"/>
                </handlers>
            </logger>

      </subsystem>
</profile>

For Hot deployment of har Custom CAS Specific classes I have used the Hot Deployment feature of jBoss 7:

 <subsystem xmlns="urn:jboss:domain:deployment-scanner:1.1">
            <deployment-scanner path="deployments" relative-to="jboss.server.base.dir" scan-interval="5000" auto-deploy-exploded="true"/>
        </subsystem>

The basic configuration is thus completed and if anything more is required I will just jot it down whenever I will encounter it.


Now the task of custom configuration of CAS Components comes into picture.

First, I have configured the custom Authentication Handler satisfying AuthenticationHandler contract (interface) and configured the bean in deployerConfigContext.xml in the authenticationHandlers property of authenticationManager bean id. Please do not change the bean id's or property names in the configuration file as those are being used by authentication managers and other components, unless you change very one of the components.


The custom Authentication Handler which I have used here is  made to support UsernamePasswordCredentials type of Authetication.

The source code is provided below:

package com.cas.customauthhandler;

import java.sql.ResultSet;
import java.sql.SQLException;
import org.apache.log4j.Logger;
import org.jasig.cas.authentication.handler.AuthenticationException;
import org.jasig.cas.authentication.handler.AuthenticationHandler;
import org.jasig.cas.authentication.principal.Credentials;
import org.jasig.cas.authentication.principal.UsernamePasswordCredentials;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
import org.springframework.security.authentication.dao.SaltSource;
import org.springframework.security.authentication.encoding.PasswordEncoder;
import org.springframework.security.core.userdetails.UserDetails;
import com.cas.model.CustomUserDetails;
import com.cas.util.Utility;

public final class CustomCasUserNamePasswordAuthHandler implements AuthenticationHandler
{


 private SimpleJdbcTemplate jdbcTemplate;
 private SaltSource saltSource;
 private PasswordEncoder passwordEncoder;

 private Logger logger = Logger.getLogger(this.getClass().getName());

 @Override
 public boolean authenticate(Credentials credentials)
   throws AuthenticationException {
  // TODO Auto-generated method stub
  UserDetails userDetails = null;
  String encodedPassword = null;;
  try{

  logger.debug(" **** In Authenticate method **** ");
  logger.debug("Username:"+((UsernamePasswordCredentials)credentials).getUsername()+"  Password:"+((UsernamePasswordCredentials)credentials).getPassword());
   userDetails = loadUserDetailsFromDB(((UsernamePasswordCredentials)credentials).getUsername());
  logger.debug("The UserDetails obtained from DB is:"+userDetails);

   encodedPassword = getEncodedPassword(userDetails,((UsernamePasswordCredentials)credentials).getPassword());
  logger.debug(" ### The encoded Passsword is:"+encodedPassword);

  }

  catch(Exception e)
  {
   logger.error(e);
  }

  return encodedPassword.equals(userDetails.getPassword());
 }
 @Override
 public boolean supports(Credentials credentials) {
  // TODO Auto-generated method stub
  logger.debug(" **** In supports method **** ");
  return UsernamePasswordCredentials.class.isAssignableFrom(credentials.getClass());
 }


 private UserDetails loadUserDetailsFromDB(String name)
 {
  try{

  logger.debug("The jdbcTemplate in loadUserDetailsFromDB() is:"+jdbcTemplate);
 
  String sqlQuery="select * from users where name=?";
  return (UserDetails)jdbcTemplate.queryForObject(sqlQuery,new RowMapper<UserDetails>() {
 
   public UserDetails mapRow(ResultSet rs, int rowNum) throws SQLException {
    // TODO Auto-generated method stub
    return new CustomUserDetails(rs.getString(1), rs.getString(2), true, true, true, true, Utility.getAuthorities(rs.getInt(3)), rs.getString(4));
   }
  }
  ,new Object[]{name});

  }
  catch (Exception e) {
   // TODO: handle exception
   logger.error(e);
   return null;
  }
 }

 private String getEncodedPassword(UserDetails userDetails,String password)
 {
  try{
   return passwordEncoder.encodePassword(password, saltSource.getSalt(userDetails));
  }
  catch(Exception e)
  {
   logger.error(e);
   return null;
  }
 }

 public void setJdbcTemplate(SimpleJdbcTemplate jdbcTemplate) {
  this.jdbcTemplate = jdbcTemplate;
 }
 public void setSaltSource(SaltSource saltSource) {
  this.saltSource = saltSource;
 }
 public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
  this.passwordEncoder = passwordEncoder;
 }


   
}

Any repository can be used for fetching user details...(This part I am leaving out for the readers for their own convenience)

To configure the above class some additional bean configuration is required in deployerConfigContext.xml.
They are as follows:

<bean id="dataSourceBean" class="org.apache.commons.dbcp.BasicDataSource">
  <property name="driverClassName" value="com.ibm.db2.jcc.DB2Driver"></property>
  <property name="url" value="jdbc:db2://localhost:50000/SAMPLE"></property>
  <property name="username" value="db2admin"></property>
  <property name="password" value="db2admin"></property>
  <property name="initialSize" value="10"></property>
  <property name="maxActive" value="20"></property>
    </bean>


<bean id="jdbcTemplate" class="org.springframework.jdbc.core.simple.SimpleJdbcTemplate">
   <constructor-arg ref="dataSourceBean" />
  </bean>


   <bean id="saltSource" class="org.springframework.security.authentication.dao.ReflectionSaltSource">
   <property name="userPropertyToUse" value="customSalt"></property>
   </bean>


<bean class="org.springframework.security.authentication.encoding.ShaPasswordEncoder" id="passwordEncoder"/>

After the above mentioned configurations are made and the jar containing the above classes are kept in the WEB-INF/lib in the cas.war folder, the new Custom Authentication Handler should be in action.

If any deviation is found please let me know, I will be more than happy to solve the issue.

Though I have configured CAS to use over HTTPS, but since i  faced some issues with certificate, I have accessed CAS over HTTP, (Accessing CAS over HTTP shows a warning that accessing over Non Secure Connection) I am leaving this issue for now, I will look into it whenever I am done with all posible basic Custom CAS configuration.

The next discussion will be on Custom Credential To Principal Resolver wich requires attribute Repository for fetching user attributes from the underlyinh repository. 

Comments

Popular posts from this blog

Use of @Configurable annotation.

Spring WS - Part 5

Spring WS - Part 4