大头祥子 / Spring / apache shiro集群实现(一) session共享

分享

   

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

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

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


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

  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. <!-- 自定义Realm -->  
  7. <bean id="shiroDbRealm" class="com.xxx.security.shiro.custom.ShiroDbRealm">  
  8.     <property name="credentialsMatcher" ref="customCredentialsMather"></property>  
  9. </bean>   
  10. <!-- 用户授权信息Cache(本机内存实现) -->  
  11. <bean id="memoryConstrainedCacheManager" class="org.apache.shiro.cache.MemoryConstrainedCacheManager" />    
  12.   
  13. <!-- 保证实现了Shiro内部lifecycle函数的bean执行 -->  
  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>  
  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. <!-- 自定义Realm -->  
  7. <bean id="shiroDbRealm" class="com.xxx.security.shiro.custom.ShiroDbRealm">  
  8.     <property name="credentialsMatcher" ref="customCredentialsMather"></property>  
  9. </bean>   
  10. <!-- 用户授权信息Cache(本机内存实现) -->  
  11. <bean id="memoryConstrainedCacheManager" class="org.apache.shiro.cache.MemoryConstrainedCacheManager" />    
  12.   
  13. <!-- 保证实现了Shiro内部lifecycle函数的bean执行 -->  
  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>  


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

  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. }  
  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. }  

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

  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.   
  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.   


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

  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>  
  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的储存类。

  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. }  
  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. }  
这里我们看到了,如果不自己注入sessionDAO,defaultWebSessionManager会使用MemorySessionDAO做为默认实现类,这个肯定不是我们想要的,所以这就自己动手实现sessionDAO吧。
  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<Session> 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. }  
  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<Session> 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. }  
我们自定义CustomShiroSessionDAO继承AbstractSessionDAO,实现对session操作的方法。这里为了便于扩展,我引入了一个接口ShiroSessionRepository,可以用redis、mongoDB等进行实现。


  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<Session> getAllSessions();  
  10. }  
  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<Session> getAllSessions();  
  10. }  
这个是我自己redis的ShiroSessionReposotory存储实现类:
  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<Session> getAllSessions() {  
  85.         Jedis jedis = this.getJedis();  
  86.         Set<Session> sessions = new HashSet<Session>();  
  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. }  
  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<Session> getAllSessions() {  
  85.         Jedis jedis = this.getJedis();  
  86.         Set<Session> sessions = new HashSet<Session>();  
  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. }  

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

  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" />  
  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的共享问题。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多
    喜欢该文的人也喜欢 更多

    ×
    ×

    ¥.00

    微信或支付宝扫码支付:

    开通即同意《个图VIP服务协议》

    全部>>