Monday, December 30, 2013

Apache Shiro Session Clustering

Referred shiro.apache.org

One of the very exciting things about Apache Shiro's session capabilities is that you can cluster Subject sessions natively and never need to worry again about how to cluster sessions based on your container environment. That is, if you use Shiro's native sessions and configure a session cluster, you can, say, deploy to Jetty or Tomcat in development, JBoss or Geronimo in production, or any other environment. Configure session clustering once in Shiro and it works no matter your deployment environment.

So how does it work?

Because of Shiro's POJO-based N-tiered architecture, enabling Session clustering is as simple as enabling a clustering mechanism at the Session persistence level. That is, if you configure a cluster-capable SessionDAO, the DAO can interact with a clustering mechanism and Shiro's SessionManager never needs to know about clustering concerns.
Distributed Caches
Distributed Caches such as Ehcache+TerraCottaGigaSpaces Oracle Coherence, and Memcached (and many others) already solve the distributed-data-at-the-persistence-level problem. Therefore enabling Session clustering in Shiro is as simple as configuring Shiro to use a distributed cache.
This gives you the flexibility of choosing the exact clustering mechanism that is suitable for your environment.
Cache Memory
Note that when enabling a distributed/enterprise cache to be your session clustering data store, one of the following two cases must be true:
  • The distributed cache has enough cluster-wide memory to retain all active/current sessions
  • If the distributed cache does not have enough cluster-wide memory to retain all active sessions, it must support disk overflow so sessions are not lost.
    Failure for the cache to support either of the two cases will result in sessions being randomly lost, which would likely be frustrating to end-users.

EnterpriseCacheSessionDAO

As you might expect, Shiro already provides a SessionDAO implementation that will persist data to an enterprise/distributed Cache. TheEnterpriseCacheSessionDAO expects a Shiro Cache or CacheManager to be configured on it so it can leverage the caching mechanism.
For example, in shiro.ini:
#This implementation would use your preferred distributed caching product's APIs:
activeSessionsCache = my.org.apache.shiro.cache.CacheImplementation

sessionDAO = org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO
sessionDAO.activeSessionsCache = $activeSessionsCache

securityManager.sessionManager.sessionDAO = $sessionDAO
Although you could inject a Cache instance directly to the SessionDAO as shown above, it is usually far more common to configure a generalCacheManager to use for all of Shiro's caching needs (sessions as well as authentication and authorization data). In this case, instead of configuring a Cache instance directly, you would tell the EnterpriseCacheSessionDAO the name of the cache in the CacheManager that should be used for storing active sessions.
For example:
# This implementation would use your caching product's APIs:
cacheManager = my.org.apache.shiro.cache.CacheManagerImplementation

# Now configure the EnterpriseCacheSessionDAO and tell it what
# cache in the CacheManager should be used to store active sessions:
sessionDAO = org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO
# This is the default value.  Change it if your CacheManager configured a different name:
sessionDAO.activeSessionsCacheName = shiro-activeSessionsCache
# Now have the native SessionManager use that DAO:
securityManager.sessionManager.sessionDAO = $sessionDAO

# Configure the above CacheManager on Shiro's SecurityManager
# to use it for all of Shiro's caching needs:
securityManager.cacheManager = $cacheManager
But there's something a bit strange about the above configuration. Did you notice it?
The interesting thing about this config is that nowhere in the config did we actually tell the sessionDAO instance to use a Cache orCacheManager! So how does the sessionDAO use the distributed cache?
When Shiro initializes the SecurityManager, it will check to see if the SessionDAO implements the CacheManagerAware interface. If it does, it will automatically be supplied with any available globally configured CacheManager.
So when Shiro evaluates the securityManager.cacheManager = $cacheManager line, it will discover that the EnterpriseCacheSessionDAOimplements the CacheManagerAware interface and call the setCacheManager method with your configured CacheManager as the method argument.
Then at runtime, when the EnterpriseCacheSessionDAO needs the activeSessionsCache it will ask the CacheManager instance to return it it, using the activeSessionsCacheName as the lookup key to get a Cache instance. That Cache instance (backed by your distributed/enterprise caching product's API) will be used to store and retrieve sessions for all of the SessionDAO CRUD operations.

Ehcache + Terracotta

One such distributed caching solution that people have had success with while using Shiro is the Ehcache + Terracotta pairing. See the Ehcache-hosted Distributed Caching With Terracotta documentation for full details of how to enable distributed caching with Ehcache.
Once you've got Terracotta clustering working with Ehcache, the Shiro-specific parts are very simple. Read and follow the Ehcache SessionDAO documentation, but we'll need to make a few changes
The Ehcache Session Cache Configuration referenced previously will not work - a Terracotta-specific configuration is needed. Here is an example configuration that has been tested to work correctly. Save its contents in a file and save it in an ehcache.xml file:
TerraCotta Session Clustering

    "localhost:9510"/>
"java.io.tmpdir/shiro-ehcache"/> "10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="false" diskPersistent="false" diskExpiryThreadIntervalSeconds="120"> "shiro-activeSessionCache" maxElementsInMemory="10000" eternal="true" timeToLiveSeconds="0" timeToIdleSeconds="0" diskPersistent="false" overflowToDisk="false" diskExpiryThreadIntervalSeconds="600">
Of course you will want to change your <terracottaConfig url="localhost:9510"/> entry to reference the appropriate host/port of your Terracotta server array. Also notice that, unlike the previous configuration, the ehcache-activeSessionCache element DOES NOT setdiskPersistent or overflowToDisk attributes to true. They should both be false as true values are not supported in clustered configuration.
After you've saved this ehcache.xml file, we'll need to reference it in Shiro's configuration. Assuming you've made the terracotta-specificehcache.xml file accessible at the root of the classpath, here is the final Shiro configuration that enables Terracotta+Ehcache clustering for all of Shiro's needs (including Sessions):
shiro.ini for Session Clustering with Ehcache and Terracotta
sessionDAO = org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO
# This name matches a cache name in ehcache.xml:
sessionDAO.activeSessionsCacheName = shiro-activeSessionsCache
securityManager.sessionManager.sessionDAO = $sessionDAO

# Configure The EhCacheManager:
cacheManager = org.apache.shiro.cache.ehcache.EhCacheManager
cacheManager.cacheManagerConfigFile = classpath:ehcache.xml

# Configure the above CacheManager on Shiro's SecurityManager
# to use it for all of Shiro's caching needs:
securityManager.cacheManager = $cacheManager
And remember, ORDER MATTERS. By configuring the cacheManager on the securityManager last, we ensure that the CacheManager can be propagated to all previously-configured CacheManagerAware components (such as the EnterpriseCachingSessionDAO).

No comments: