cas的session存储及实现共享的

cas的session存储及实现共享的原理:

cas在HashMapBackedSessionMappingStorage存session所以后面可以根据自己的tic,结合cookie反向生成session放到新的web服务器中

集群思想:cas通过redis覆写可以实现多节点cas的集群(都是用redis存储session,不再使用HashMapBackedSessionMappingStorage这个内部的map存)

cas是开源的登录认证方案,可以实现多个web应用的单点登录。

随着用户量的增加,web应用需要部署多个实例,要实现不同应用、多实例的共享session,需要先了解cas的logout机制。简单的说,web应用在接入cas的时候需要继承cas-client-core,这个模块完成的事情如下:

 

  • 拦截到web应用的请求,验证登录状态,若未登录则跳转到登录页
  • 登录成功,web应用的tomcat存储session,cas-server保存TGT信息,cas-client-core保存ST和session的对应关系
  • 登出时由cas-server返回ST信息,cas-client-core根据ST删除自己存储在内存的ST和session信息
要实现共享session,就需要解决以下两个问题:
  • tomcat共享session
  • cas-client-core共享ST和session的对应关系

tomcat共享session

tomcat默认的session是存在在内存的,因此要实现共享就需要实现session存储到数据库,考虑到用户操作需要频繁读取session,因此redis很适合用来实现session统一存储。
tomcat-redis-session-manager是开源的tomcat共享session插件,这里需要注意要下载比较新的master版本,我当时是下了稳定版本tomcat-redis-session-manager-1.2-tomcat-7-java-7,结果一直在死循环,阅读源码才修改了其中的bug,后面发现master已经修复bug。
我用的插件版本如下:
  • commons-pool-1.6.jar
  • jedis-2.0.0.jar
  • 修改bug后的tomcat-redis-session-manager-1.2-tomcat-7-java-7.jar
然后按照官网的说明,只需要在tomcat的context.xml简单加上配置就ok

cas-client-core共享ST和session

cas-client-core利用HashMapBackedSessionMappingStorage实现了ST和session的内存存储,因此很简单,我们只需要实现使用redis存储ST和session的缓存,替换该接口即可。
这里在序列化存储的时候碰到头疼的问题,StandardSessionFacade没有实现序列化,如果改动StandardSessionFacade需要重新编译catalina的jar包,比较麻烦所以没有选择该方案。
那怎么办呢?在集成了tomcat-redis-session-manager插件后,tomcat使用的session最终实例其实还是RedisSession,这个必然实现了序列化接口,因此我们只需要存储这个实例即可。问题来了,StandardSessionFacade的变量session为private,真是处处坑。。只能用java反射来获取了。以下是redis保存ST和session的源码:
[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. @Override  
  2. public void addSessionById(String mappingId, HttpSession session) {  
  3.     String STKey = getKey(mappingId);  
  4.     StandardSessionFacade standardSessionFacade = (StandardSessionFacade) session;  
  5.     RedisSession redisSession = null;  
  6.     try {  
  7.         redisSession = (RedisSession) getValue(standardSessionFacade, "session");  
  8.     } catch (IllegalAccessException e) {  
  9.         e.printStackTrace();  
  10.     } catch (NoSuchFieldException e) {  
  11.         e.printStackTrace();  
  12.     }  
  13.     if (null == redisSession) {  
  14.         log.error("get redisSession fail");  
  15.         return;  
  16.     }  
  17.   
  18.     sessionRedisTemplate.opsForValue().set(STKey, jdkSerializer.serialize(redisSession));  
  19.     String sessionKey = getKey(session.getId());  
  20.     stringRedisTemplate.opsForValue().set(sessionKey, STKey);  
  21.     log.debug("cas-client add session, mappingId:" + mappingId + " sessionId:" + session.getId());  
  22. }  
保存ok了,接下来看看删除,先来看看SingleSignOutHandler的删除代码:
[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. public void destroySession(final HttpServletRequest request) {  
  2.     final String logoutMessage = CommonUtils.safeGetParameter(request, this.logoutParameterName);  
  3.     if (log.isTraceEnabled()) {  
  4.         log.trace("Logout request:\n" + logoutMessage);  
  5.     }  
  6.   
  7.     final String token = XmlUtils.getTextForElement(logoutMessage, "SessionIndex");  
  8.     if (CommonUtils.isNotBlank(token)) {  
  9.         final HttpSession session = this.sessionMappingStorage.removeSessionByMappingId(token);  
  10.   
  11.         if (session != null) {  
  12.             String sessionID = session.getId();  
  13.   
  14.             if (log.isDebugEnabled()) {  
  15.                 log.debug("Invalidating session [" + sessionID + "] for token [" + token + "]");  
  16.             }  
  17.             try {  
  18.                 session.invalidate();  
  19.             } catch (final IllegalStateException e) {  
  20.                 log.debug("Error invalidating session.", e);  
  21.             }  
  22.         }  
  23.     }  
  24. }  
invalidate方法会调用StandardSession的expire方法,此处需要利用manager获取上下文信息,但是manager无法存储(测试过使用json序列化或者java 序列化均无法存储manager),因此 invalidate不会真正去执行,看了下代码,只是通知一些监听者。so,感觉问题不大,ST和session对应关系信息删除了,接下来就是删除tomcat的session,干脆用了比较trick的方法,在cas-client-core直接删除tomcat的session。附上删除的代码:
 
[java]  view plain  copy
 
  在CODE上查看代码片 派生到我的代码片
  1. @Override  
  2. public HttpSession removeSessionByMappingId(String mappingId) {  
  3.     String STKey = getKey(mappingId);  
  4.     log.debug("cas-client remove session, STKey:" + STKey);  
  5.     RedisSession session = null;  
  6.     byte[] value = (byte[]) sessionRedisTemplate.opsForValue().get(STKey);  
  7.     session = (RedisSession) jdkSerializer.deserialize(value);  
  8.     if (null == session) {  
  9.         log.error("session is null");  
  10.         return null;  
  11.     }  
  12.   
  13.     removeBySessionById(session.getId());  
  14.     sessionRedisTemplate.delete(session.getId());  
  15.     log.debug("delete session:" + session.getId());  
  16.   
  17.     return session;  
  18. }  
  19.   
  20. @Override  
  21. public void removeBySessionById(String sessionId) {  
  22.     log.debug("Attempting to remove Session=[" + sessionId + "]");  
  23.     String sessionKey = getKey(sessionId);  
  24.     String st = stringRedisTemplate.opsForValue().get(sessionKey);  
  25.   
  26.     if (log.isDebugEnabled()) {  
  27.         if (st != null) {  
  28.             log.debug("Found mapping for session.  Session Removed.");  
  29.         } else {  
  30.             log.debug("No mapping for session found.  Ignoring.");  
  31.         }  
  32.     }  
  33.     stringRedisTemplate.delete(sessionKey);  
  34.     sessionRedisTemplate.delete(st);  
  35. }  
实现有点trick,测试暂时没有发现问题,两个实例能够正常登陆、使用和退出。

猜你喜欢

转载自yuhuiblog6338999322098842.iteye.com/blog/2373145