Apache shiro集群实现 (六)分布式集群系统下的高可用session解决方案---Session共享

Apache shiro集群实现 (一) shiro入门介绍

Apache shiro集群实现 (二) shiro 的INI配置

Apache shiro集群实现 (三)shiro身份认证(Shiro Authentication)

Apache shiro集群实现 (四)shiro授权(Authentication)--访问控制

Apache shiro集群实现 (五)分布式集群系统下的高可用session解决方案

Apache shiro集群实现 (六)分布式集群系统下的高可用session解决方案---Session共享

Apache shiro集群实现(七)分布式集群系统下---cache共享
Apache shiro集群实现 (八) web集群时session同步的3种方法


      Apache Shiro的基本配置和构成这里就不详细说明了,其官网有说明文档,这里仅仅说明集群的解决方案,详细配置:shiro web config

    Apache Shiro集群要解决2个问题,一个是session的共享问题,一个是授权信息的cache共享问题,官网给的例子是Ehcache的实现,在配置说明上不算很详细,我这里用nosql(redis)替代了ehcache做了session和cache的存储。


shiro spring的默认配置(单机,非集群)


  
      
      
  
  
  
  
      
   
  
    
  
  
  
  
  
      
      
      
      
          
        /login = authc  
        /logout = logout              
      
      
 

上面的配置是shiro非集群下的配置,DefaultWebSecurityManager类不需要注入sessionManager属性,它会使用默认的sessionManager类,请看源码


    public DefaultWebSecurityManager() {  
            super();  
            ((DefaultSubjectDAO) this.subjectDAO).setSessionStorageEvaluator(new DefaultWebSessionStorageEvaluator());  
            this.sessionMode = HTTP_SESSION_MODE;  
            setSubjectFactory(new DefaultWebSubjectFactory());  
            setRememberMeManager(new CookieRememberMeManager());  
            setSessionManager(new ServletContainerSessionManager());  
    }  

在最后一行,set了默认的servlet容器实现的sessionManager,sessionManager会管理session的创建、删除等等。如果我们需要让session在集群中共享,就需要替换这个默认的sessionManager。在其官网上原话是这样的:


    Native Sessions  
      
    If you want your session configuration settings and clustering to be portable across servlet containers  
    (e.g. Jetty in testing, but Tomcat or JBoss in production), or you want to control specific session/clustering   
    features, you can enable Shiro's native session management.  
      
    The word 'Native' here means that Shiro's own enterprise session management implementation will be used to support   
    all Subject and HttpServletRequest sessions and bypass the servlet container completely. But rest assured - Shiro   
    implements the relevant parts of the Servlet specification directly so any existing web/http related code works as   
    expected and never needs to 'know' that Shiro is transparently managing sessions.  
      
    DefaultWebSessionManager  
      
    To enable native session management for your web application, you will need to configure a native web-capable   
    session manager to override the default servlet container-based one. You can do that by configuring an instance of   
    DefaultWebSessionManager on Shiro's SecurityManager.   

我们可以看到如果要用集群,就需要用本地会话,这里shiro给我准备了一个默认的native session manager,DefaultWebSessionManager,所以我们要修改spring配置文件,注入DefaultWebSessionManager


      
          
          
          
      
      
          
      
我们继续看DefaultWebSessionManager的源码,发现其父类DefaultSessionManager中有sessionDAO属性,这个属性是真正实现了session储存的类,这个就是我们自己实现的redis session的储存类。


    protected SessionDAO sessionDAO;  
      
    private CacheManager cacheManager;  
      
    private boolean deleteInvalidSessions;  
      
    public DefaultSessionManager() {  
        this.deleteInvalidSessions = true;  
        this.sessionFactory = new SimpleSessionFactory();  
        this.sessionDAO = new MemorySessionDAO();  
    }  

这里我们看到了,如果不自己注入sessionDAO,defaultWebSessionManager会使用MemorySessionDAO做为默认实现类,这个肯定不是我们想要的,所以这就自己动手实现sessionDAO吧。


package com.tgb.itoo.authority.cache;

import java.io.Serializable;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.eis.AbstractSessionDAO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class RedisSessionDAO extends AbstractSessionDAO {

	private static Logger logger = LoggerFactory.getLogger(RedisSessionDAO.class);
	/**
	 * shiro-redis的session对象前缀
	 */
	private RedisManager redisManager;
	
	/**
	 * The Redis key prefix for the sessions 
	 */
	private String keyPrefix = "shiro_redis_session:";
	
	@Override
	public void update(Session session) throws UnknownSessionException {
		this.saveSession(session);
	}
	
	/**
	 * save session
	 * @param session
	 * @throws UnknownSessionException
	 */
	private void saveSession(Session session) throws UnknownSessionException{
		if(session == null || session.getId() == null){
			logger.error("session or session id is null");
			return;
		}
		
		byte[] key = getByteKey(session.getId());
		byte[] value = SerializeUtils.serialize(session);
		session.setTimeout(redisManager.getExpire()*1000);		
		this.redisManager.set(key, value, redisManager.getExpire());
	}

	@Override
	public void delete(Session session) {
		if(session == null || session.getId() == null){
			logger.error("session or session id is null");
			return;
		}
		redisManager.del(this.getByteKey(session.getId()));

	}

	//用来统计当前活动的session
	@Override
	public Collection getActiveSessions() {
		Set sessions = new HashSet();
		
		Set keys = redisManager.keys(this.keyPrefix + "*");
		if(keys != null && keys.size()>0){
			for(byte[] key:keys){
				Session s = (Session)SerializeUtils.deserialize(redisManager.get(key));
				sessions.add(s);
			}
		}
		
		return sessions;
	}

	@Override
	protected Serializable doCreate(Session session) {
		Serializable sessionId = this.generateSessionId(session);  
        this.assignSessionId(session, sessionId);
        this.saveSession(session);
		return sessionId;
	}

	@Override
	protected Session doReadSession(Serializable sessionId) {
		if(sessionId == null){
			logger.error("session id is null");
			return null;
		}
		
		Session s = (Session)SerializeUtils.deserialize(redisManager.get(this.getByteKey(sessionId)));
		return s;
	}
	
	/**
	 * 获得byte[]型的key
	 * @param key
	 * @return
	 */
	private byte[] getByteKey(Serializable sessionId){
		String preKey = this.keyPrefix + sessionId;
		return preKey.getBytes();
	}

	public RedisManager getRedisManager() {
		return redisManager;
	}

	public void setRedisManager(RedisManager redisManager) {
		this.redisManager = redisManager;
		
		/**
		 * 初始化redisManager
		 */
		this.redisManager.init();
	}

	/**
	 * Returns the Redis session keys
	 * prefix.
	 * @return The prefix
	 */
	public String getKeyPrefix() {
		return keyPrefix;
	}

	/**
	 * Sets the Redis sessions key 
	 * prefix.
	 * @param keyPrefix The prefix
	 */
	public void setKeyPrefix(String keyPrefix) {
		this.keyPrefix = keyPrefix;
	}
}


我们自定义RedisSessionDAO继承AbstractSessionDAO,实现对session操作的方法,可以用redis、mongoDB等进行实现。


这个是自己redis的RedisCacheManager存储实现类:


package com.tgb.itoo.authority.cache;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.apache.shiro.cache.CacheManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class RedisCacheManager implements CacheManager{

	private static final Logger logger = LoggerFactory
			.getLogger(RedisCacheManager.class);

	// fast lookup by name map
	private final ConcurrentMap caches = new ConcurrentHashMap();

	private RedisManager redisManager;

	/**
	 * The Redis key prefix for caches 
	 */
	private String keyPrefix = "shiro_redis_cache:";
	
	/**
	 * Returns the Redis session keys
	 * prefix.
	 * @return The prefix
	 */
	public String getKeyPrefix() {
		return keyPrefix;
	}

	/**
	 * Sets the Redis sessions key 
	 * prefix.
	 * @param keyPrefix The prefix
	 */
	public void setKeyPrefix(String keyPrefix) {
		this.keyPrefix = keyPrefix;
	}
	
	@Override
	public  Cache getCache(String name) throws CacheException {
		logger.debug("获取名称为: " + name + " 的RedisCache实例");
		
		Cache c = caches.get(name);
		
		if (c == null) {

			// initialize the Redis manager instance
			redisManager.init();
			
			// create a new cache instance
			c = new RedisCache(redisManager, keyPrefix);
			
			// add it to the cache collection
			caches.put(name, c);
		}
		return c;
	}

	public RedisManager getRedisManager() {
		return redisManager;
	}

	public void setRedisManager(RedisManager redisManager) {
		this.redisManager = redisManager;
	}
}


这样RedisSessionDAO我们就完成了,下面继续修改我们spring配置文件:





	
	
	
		
			
				classpath:config/shiro-cas.properties
			
		
	
	
	
	
	








	
	
		
		
		
		
			
				
					
						
						
					
				
			
		
		
		
			
				/message.jsp=anon
				/logout=logout
				/shiro-cas=casFilter
				/** =user
			
		
	
	

	
	

	
	
		
		
		
		
		

		
		

		
		
	


	

	
	
	
	
	

	
	
		
	
	
	
		
	

	

	



	
	
		
	



	
	
	
		
		
		
		

		
		

		
		
		
		
		
		

	


	
	
		
		
		
		
		
	







	
	
		

		
		
		
		
	

	

	
	
	
	

	
	
	
		
	



这样第一个问题,session的共享问题我们就解决好了,下一篇介绍另一个问题,cache的共享问题。

你可能感兴趣的:(Shiro,Apache,shiro)