apache shiro集群实现(一) session共享

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

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


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

[html] view plain copy print ?
  1. <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">  
  2.     <property name="realm" ref="shiroDbRealm" />  
  3.     <property name="cacheManager" ref="memoryConstrainedCacheManager" />  
  4. bean>  
  5.   
  6.   
  7. <bean id="shiroDbRealm" class="com.xxx.security.shiro.custom.ShiroDbRealm">  
  8.     <property name="credentialsMatcher" ref="customCredentialsMather">property>  
  9. bean>   
  10.   
  11. <bean id="memoryConstrainedCacheManager" class="org.apache.shiro.cache.MemoryConstrainedCacheManager" />    
  12.   
  13.   
  14. <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>  
  15.   
  16. <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">  
  17.     <property name="securityManager" ref="securityManager" />  
  18.     <property name="loginUrl" value="/login" />  
  19.     <property name="successUrl" value="/project" />  
  20.     <property name="filterChainDefinitions">  
  21.         <value>  
  22.         /login = authc  
  23.         /logout = logout              
  24.     value>  
  25.     property>  
  26. bean>  

    
    




    
 

	





    
    
    
    
        
	    /login = authc
	    /logout = logout			
	
    


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

[java] view plain copy print ?
  1. public DefaultWebSecurityManager() {  
  2.         super();  
  3.         ((DefaultSubjectDAO) this.subjectDAO).setSessionStorageEvaluator(new DefaultWebSessionStorageEvaluator());  
  4.         this.sessionMode = HTTP_SESSION_MODE;  
  5.         setSubjectFactory(new DefaultWebSubjectFactory());  
  6.         setRememberMeManager(new CookieRememberMeManager());  
  7.         setSessionManager(new ServletContainerSessionManager());  
  8. }  
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。在其官网上原话是这样的:

[html] view plain copy print ?
  1. Native Sessions  
  2.   
  3. If you want your session configuration settings and clustering to be portable across servlet containers  
  4. (e.g. Jetty in testing, but Tomcat or JBoss in production), or you want to control specific session/clustering   
  5. features, you can enable Shiro's native session management.  
  6.   
  7. The word 'Native' here means that Shiro's own enterprise session management implementation will be used to support   
  8. all Subject and HttpServletRequest sessions and bypass the servlet container completely. But rest assured - Shiro   
  9. implements the relevant parts of the Servlet specification directly so any existing web/http related code works as   
  10. expected and never needs to 'know' that Shiro is transparently managing sessions.  
  11.   
  12. DefaultWebSessionManager  
  13.   
  14. To enable native session management for your web application, you will need to configure a native web-capable   
  15. session manager to override the default servlet container-based one. You can do that by configuring an instance of   
  16. DefaultWebSessionManager on Shiro's SecurityManager.   
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

[html] view plain copy print ?
  1. <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">  
  2.     <property name="sessionManager" ref="defaultWebSessionManager" />  
  3.     <property name="realm" ref="shiroDbRealm" />  
  4.     <property name="cacheManager" ref="memoryConstrainedCacheManager" />  
  5. bean>  
  6. <bean id="defaultWebSessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">  
  7.     <property name="globalSessionTimeout" value="1200000" />  
  8. bean>  

    
    
    


    

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

[java] view plain copy print ?
  1. protected SessionDAO sessionDAO;  
  2.   
  3. private CacheManager cacheManager;  
  4.   
  5. private boolean deleteInvalidSessions;  
  6.   
  7. public DefaultSessionManager() {  
  8.     this.deleteInvalidSessions = true;  
  9.     this.sessionFactory = new SimpleSessionFactory();  
  10.     this.sessionDAO = new MemorySessionDAO();  
  11. }  
 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吧。
[java] view plain copy print ?
  1. public class CustomShiroSessionDAO extends AbstractSessionDAO {  
  2.   
  3.     private ShiroSessionRepository shiroSessionRepository;  
  4.   
  5.     public ShiroSessionRepository getShiroSessionRepository() {  
  6.         return shiroSessionRepository;  
  7.     }  
  8.   
  9.     public void setShiroSessionRepository(  
  10.             ShiroSessionRepository shiroSessionRepository) {  
  11.         this.shiroSessionRepository = shiroSessionRepository;  
  12.     }  
  13.   
  14.     @Override  
  15.     public void update(Session session) throws UnknownSessionException {  
  16.         getShiroSessionRepository().saveSession(session);  
  17.     }  
  18.   
  19.     @Override  
  20.     public void delete(Session session) {  
  21.         if (session == null) {  
  22.             LoggerUtil.error(CustomShiroSessionDAO.class,  
  23.                     "session can not be null,delete failed");  
  24.             return;  
  25.         }  
  26.         Serializable id = session.getId();  
  27.         if (id != null)  
  28.             getShiroSessionRepository().deleteSession(id);  
  29.     }  
  30.   
  31.     @Override  
  32.     public Collection getActiveSessions() {  
  33.         return getShiroSessionRepository().getAllSessions();  
  34.     }  
  35.   
  36.     @Override  
  37.     protected Serializable doCreate(Session session) {  
  38.         Serializable sessionId = this.generateSessionId(session);  
  39.         this.assignSessionId(session, sessionId);  
  40.         getShiroSessionRepository().saveSession(session);  
  41.         return sessionId;  
  42.     }  
  43.   
  44.     @Override  
  45.     protected Session doReadSession(Serializable sessionId) {  
  46.         return getShiroSessionRepository().getSession(sessionId);  
  47.     }  
  48. }  
public class CustomShiroSessionDAO extends AbstractSessionDAO {

	private ShiroSessionRepository shiroSessionRepository;

	public ShiroSessionRepository getShiroSessionRepository() {
		return shiroSessionRepository;
	}

	public void setShiroSessionRepository(
			ShiroSessionRepository shiroSessionRepository) {
		this.shiroSessionRepository = shiroSessionRepository;
	}

	@Override
	public void update(Session session) throws UnknownSessionException {
		getShiroSessionRepository().saveSession(session);
	}

	@Override
	public void delete(Session session) {
		if (session == null) {
			LoggerUtil.error(CustomShiroSessionDAO.class,
					"session can not be null,delete failed");
			return;
		}
		Serializable id = session.getId();
		if (id != null)
			getShiroSessionRepository().deleteSession(id);
	}

	@Override
	public Collection getActiveSessions() {
		return getShiroSessionRepository().getAllSessions();
	}

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

	@Override
	protected Session doReadSession(Serializable sessionId) {
		return getShiroSessionRepository().getSession(sessionId);
	}
}
我们自定义CustomShiroSessionDAO继承AbstractSessionDAO ,实现对session操作的方法。这里为了便于扩展,我引入了一个接口ShiroSessionRepository,可以用redis、mongoDB等进行实现。


[java] view plain copy print ?
  1. public interface ShiroSessionRepository {  
  2.   
  3.     void saveSession(Session session);  
  4.   
  5.     void deleteSession(Serializable sessionId);  
  6.   
  7.     Session getSession(Serializable sessionId);  
  8.   
  9.     Collection getAllSessions();  
  10. }  
public interface ShiroSessionRepository {

	void saveSession(Session session);

	void deleteSession(Serializable sessionId);

	Session getSession(Serializable sessionId);

	Collection getAllSessions();
}
这个是我自己redis的 ShiroSessionReposotory存储实现类:
[java] view plain copy print ?
  1. public class JedisShiroSessionRepository extends JedisManager implements  
  2.         ShiroSessionRepository {  
  3.   
  4.     /** 
  5.      * redis session key前缀 
  6.      */  
  7.     private final String REDIS_SHIRO_SESSION = "shiro-session:";  
  8.   
  9.     @Autowired  
  10.     private JedisPool jedisPool;  
  11.   
  12.     @Override  
  13.     protected JedisPool getJedisPool() {  
  14.         return jedisPool;  
  15.     }  
  16.   
  17.     @Override  
  18.     protected JedisDataType getJedisDataType() {  
  19.         return JedisDataType.SESSION_CACHE;  
  20.     }  
  21.   
  22.     @Override  
  23.     public void saveSession(Session session) {  
  24.         if (session == null || session.getId() == null) {  
  25.             LoggerUtil.error(JedisShiroSessionRepository.class,  
  26.                     "session或者session id为空");  
  27.             return;  
  28.         }  
  29.         byte[] key = SerializeUtil  
  30.                 .serialize(getRedisSessionKey(session.getId()));  
  31.         byte[] value = SerializeUtil.serialize(session);  
  32.         Jedis jedis = this.getJedis();  
  33.         try {  
  34.             Long timeOut = session.getTimeout() / 1000;  
  35.             jedis.set(key, value);  
  36.             jedis.expire(key, Integer.parseInt(timeOut.toString()));  
  37.         } catch (JedisException e) {  
  38.             LoggerUtil.error(JedisShiroSessionRepository.class"保存session失败",  
  39.                     e);  
  40.         } finally {  
  41.             this.returnResource(jedis);  
  42.         }  
  43.     }  
  44.   
  45.     @Override  
  46.     public void deleteSession(Serializable id) {  
  47.         if (id == null) {  
  48.             LoggerUtil.error(JedisShiroSessionRepository.class"id为空");  
  49.             return;  
  50.         }  
  51.         Jedis jedis = this.getJedis();  
  52.         try {  
  53.             jedis.del(SerializeUtil.serialize(getRedisSessionKey(id)));  
  54.         } catch (JedisException e) {  
  55.             LoggerUtil.error(JedisShiroSessionRepository.class"删除session失败",  
  56.                     e);  
  57.         } finally {  
  58.             this.returnResource(jedis);  
  59.         }  
  60.     }  
  61.   
  62.     @Override  
  63.     public Session getSession(Serializable id) {  
  64.         if (id == null) {  
  65.             LoggerUtil.error(JedisShiroSessionRepository.class"id为空");  
  66.             return null;  
  67.         }  
  68.         Session session = null;  
  69.         Jedis jedis = this.getJedis();  
  70.         try {  
  71.             byte[] value = jedis.get(SerializeUtil  
  72.                     .serialize(getRedisSessionKey(id)));  
  73.             session = SerializeUtil.deserialize(value, Session.class);  
  74.         } catch (JedisException e) {  
  75.             LoggerUtil.error(JedisShiroSessionRepository.class"获取id为" + id  
  76.                     + "的session失败", e);  
  77.         } finally {  
  78.             this.returnResource(jedis);  
  79.         }  
  80.         return session;  
  81.     }  
  82.   
  83.     @Override  
  84.     public Collection getAllSessions() {  
  85.         Jedis jedis = this.getJedis();  
  86.         Set sessions = new HashSet();  
  87.         try {  
  88.             Set<byte[]> byteKeys = jedis.keys(SerializeUtil  
  89.                     .serialize(this.REDIS_SHIRO_SESSION + "*"));  
  90.             if (byteKeys != null && byteKeys.size() > 0) {  
  91.                 for (byte[] bs : byteKeys) {  
  92.                     Session s = SerializeUtil.deserialize(jedis.get(bs),  
  93.                             Session.class);  
  94.                     sessions.add(s);  
  95.                 }  
  96.             }  
  97.         } catch (JedisException e) {  
  98.             LoggerUtil.error(JedisShiroSessionRepository.class,  
  99.                     "获取所有session失败", e);  
  100.         } finally {  
  101.             this.returnResource(jedis);  
  102.         }  
  103.         return sessions;  
  104.     }  
  105.   
  106.     /** 
  107.      * 获取redis中的session key 
  108.      *  
  109.      * @param sessionId 
  110.      * @return 
  111.      */  
  112.     private String getRedisSessionKey(Serializable sessionId) {  
  113.         return this.REDIS_SHIRO_SESSION + sessionId;  
  114.     }  
  115.   
  116. }  
public class JedisShiroSessionRepository extends JedisManager implements
		ShiroSessionRepository {

	/**
	 * redis session key前缀
	 */
	private final String REDIS_SHIRO_SESSION = "shiro-session:";

	@Autowired
	private JedisPool jedisPool;

	@Override
	protected JedisPool getJedisPool() {
		return jedisPool;
	}

	@Override
	protected JedisDataType getJedisDataType() {
		return JedisDataType.SESSION_CACHE;
	}

	@Override
	public void saveSession(Session session) {
		if (session == null || session.getId() == null) {
			LoggerUtil.error(JedisShiroSessionRepository.class,
					"session或者session id为空");
			return;
		}
		byte[] key = SerializeUtil
				.serialize(getRedisSessionKey(session.getId()));
		byte[] value = SerializeUtil.serialize(session);
		Jedis jedis = this.getJedis();
		try {
			Long timeOut = session.getTimeout() / 1000;
			jedis.set(key, value);
			jedis.expire(key, Integer.parseInt(timeOut.toString()));
		} catch (JedisException e) {
			LoggerUtil.error(JedisShiroSessionRepository.class, "保存session失败",
					e);
		} finally {
			this.returnResource(jedis);
		}
	}

	@Override
	public void deleteSession(Serializable id) {
		if (id == null) {
			LoggerUtil.error(JedisShiroSessionRepository.class, "id为空");
			return;
		}
		Jedis jedis = this.getJedis();
		try {
			jedis.del(SerializeUtil.serialize(getRedisSessionKey(id)));
		} catch (JedisException e) {
			LoggerUtil.error(JedisShiroSessionRepository.class, "删除session失败",
					e);
		} finally {
			this.returnResource(jedis);
		}
	}

	@Override
	public Session getSession(Serializable id) {
		if (id == null) {
			LoggerUtil.error(JedisShiroSessionRepository.class, "id为空");
			return null;
		}
		Session session = null;
		Jedis jedis = this.getJedis();
		try {
			byte[] value = jedis.get(SerializeUtil
					.serialize(getRedisSessionKey(id)));
			session = SerializeUtil.deserialize(value, Session.class);
		} catch (JedisException e) {
			LoggerUtil.error(JedisShiroSessionRepository.class, "获取id为" + id
					+ "的session失败", e);
		} finally {
			this.returnResource(jedis);
		}
		return session;
	}

	@Override
	public Collection getAllSessions() {
		Jedis jedis = this.getJedis();
		Set sessions = new HashSet();
		try {
			Set byteKeys = jedis.keys(SerializeUtil
					.serialize(this.REDIS_SHIRO_SESSION + "*"));
			if (byteKeys != null && byteKeys.size() > 0) {
				for (byte[] bs : byteKeys) {
					Session s = SerializeUtil.deserialize(jedis.get(bs),
							Session.class);
					sessions.add(s);
				}
			}
		} catch (JedisException e) {
			LoggerUtil.error(JedisShiroSessionRepository.class,
					"获取所有session失败", e);
		} finally {
			this.returnResource(jedis);
		}
		return sessions;
	}

	/**
	 * 获取redis中的session key
	 * 
	 * @param sessionId
	 * @return
	 */
	private String getRedisSessionKey(Serializable sessionId) {
		return this.REDIS_SHIRO_SESSION + sessionId;
	}

}

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

[html] view plain copy print ?
  1. <bean id="defaultWebSessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">  
  2.     <property name="globalSessionTimeout" value="1200000" />  
  3.     <property name="sessionDAO" ref="customShiroSessionDAO" />  
  4. bean>  
  5.   
  6. <bean id="customShiroSessionDAO" class="com.xxx.security.shiro.custom.session.CustomShiroSessionDAO">  
  7.     <property name="shiroSessionRepository" ref="jedisShiroSessionRepository" />  
  8. bean>  
  9.       
  10. <bean id="jedisShiroSessionRepository" class="com.xxx.security.shiro.custom.session.JedisShiroSessionRepository" />  

    
    



    

	



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

你可能感兴趣的:(架构师,apache)