Thursday, December 27, 2012

Clustered Spring SessionRegistry: Spring Security Concurrent sessions in a clustered environment



I was recently working with a client whose application has a login module developed on spring security that was to be clustered using web sessions. The requirement was that if the user logs in twice into the system, the previous session should be invalidated and redirected to an error page. 
Spring security provides this functionality out of the box using ConcurrentSessionFilter. This filter makes use of an internal session registry that keep track of what users have logged in and their session details. 

However while integrating with Terracotta web sessions we found that this did not work. After investigation we found that the default session registry implementation was not clustered. I.e. it was creating local copies of session on each server. Thus the user was able to login multiple times.

In order to make this work the SessionRegistry work in a distributed environment or in front of a load balancer, we created a custom session registry on top of Ehcache. All the session details are now populated into Ehcache and get replicated across the servers. Hence a login session created on one server was now visible to the other servers and we could invalidate the previous session.

Attached project is tested on Terracotta 3.7.2, Jboss 7, Spring 3.0.5.

Download the code here.

Tuesday, September 4, 2012

Plugging in Ehcache into iBATIS


As you probably know that Ehcache is the Hibernate’s default 2nd level cache. Integrating Ehcache into iBATIS is relatively easy using iBATIS’s CacheController interface. Using the CacheController interface you can plugin your own custom caching solution or plug in any third party caching solution. The javadoc for CacheController is here. In order to plugin Ehcache, you must implement the CacheController interface as follows.

Implementing the CacheController:

import java.io.File;
import java.net.URL;
import java.util.Properties;

import net.sf.ehcache.Element;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Cache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.ibatis.sqlmap.engine.cache.CacheModel;
import com.ibatis.sqlmap.engine.cache.CacheController;


public class EhcacheIbatisCacheController implements CacheController {
    final private static Logger logger = LoggerFactory.getLogger(EhcacheIbatisCacheController.class);
    
    /** EhCache CacheManager. */
    private CacheManager cacheManager;

    /**
     * Flush a cache model.
     * @param cacheModel - the model to flush.
     */
    public void flush(CacheModel cacheModel) {
        getCache(cacheModel).removeAll();
    }

    /**
     * Get an object from a cache model.
     * @param cacheModel - the model.
     * @param key        - the key to the object.
     * @return the object if in the cache.
     */
    public Object getObject(CacheModel cacheModel, Object key) {
        Object result = null;
        try {
            Element element = getCache(cacheModel).get(key.toString());
            if (element != null) {
                result = element.getObjectValue();
            }
        }
        catch(Exception e) {
            logger.debug("cache miss, will check in db");
        }
        return result;

    }

    /**
     * Put an object into a cache model.
     * @param cacheModel - the model to add the object to.
     * @param key        - the key to the object.
     * @param object     - the value to add.
     */
    public void putObject(CacheModel cacheModel, Object key, Object object) {
        getCache(cacheModel).put(new Element(key.toString(), object));
    }

    /**
     * Remove an object from a cache model.
     * @param cacheModel - the model to remove the object from.
     * @param key        - the key to the object.
     * @return the removed object.
     */
    public Object removeObject(CacheModel cacheModel, Object key) {
        Object result = this.getObject(cacheModel, key.toString());
        getCache(cacheModel).remove(key.toString());
        return result;
    }

    /**
     * Configure a cache controller. Initialize the Cache Manager of Ehcache
     * @param props - the properties object containing configuration information.
     */
    public void setProperties(Properties props) {

        String configFile = props.getProperty("configFile");
        File file = new File(configFile);
        if(file.exists()) {
            cacheManager = CacheManager.create(file.getAbsolutePath());
        }
        else {
            URL url = getClass().getResource(configFile);
            cacheManager = CacheManager.create(url);
        }
    }

    /**
     * Gets a ehcache based on an iBatis cache Model.
     * @param cacheModel - the cache model.
     * @return the Cache.
     */
    private Cache getCache(CacheModel cacheModel) {
        String cacheName = cacheModel.getId();
        return cacheManager.getCache(cacheName);
    }

}

Each method provides access to the CacheModel that controls the cache so you can access parameters in the CacheModel when required.

Registering EhcacheIbatisController with iBATIS: 


CacheModels and CacheControllers must be registered in the ibatis xml. 1st create an alias for the Ehcache controller as follows:


Now we need to apply the Cache controller to a CacheModel definition in the xml.