基于Redis的CAS集群

来源:互联网 发布:电力调度电话交换网络 编辑:程序博客网 时间:2024/05/22 05:13

单点登录(SSO)是复杂应用系统的基本需求,Yale CAS是目前常用的开源解决方案。CAS认证中心,基于其特殊作用,自然会成为整个应用系统的核心,所有应用系统的认证工作,都将请求到CAS来完成。因此CAS服务器是整个应用的关键节点,CAS发生故障,所有系统都将陷入瘫痪。同时,CAS的负载能力要足够强,能够承担所有的认证请求响应。利用负载均衡和集群技术,不仅能克服CAS单点故障,同时将认证请求分布到多台CAS服务器上,有效减轻单台CAS服务器的请求压力。下面将基于CAS 3.4.5来讨论下CAS集群。

CAS的工作原理,主要是基于票据(Ticket)来实现的(参见 CAS基本原理)。CAS票据,存储在TicketRegistry中,因此要想实现CAS Cluster, 必须要多台CAS之间共享所有的Ticket,采用统一的TicketRegistry,可以达到此目的。  缺省的CAS实现中,TicketRegistry在内存中实现,不同的CAS服务器有自己单独的TicketRegistry,因此是不支持分布式集群的。但CAS提供了支持TicketRegistry分布式的接口org.jasig.cas.ticket.registry.AbstractDistributedTicketRegistry,我们可以实现这个接口实现多台CAS服务器TicketRegistry共享,从而实现CAS集群。

同时,较新版本CAS使用SpringWebFlow作为认证流程,而webflow需要使用session存储流程相关信息,因此实现CAS集群,我们还得需要让不同服务器的session进行共享。

我们采用内存数据库Redis来实现TicketRegistry,让多个CAS服务器共用同一个TicketRegistry。同样方法,我们让session也存储在Redis中,达到共享session的目的。下面就说说如何用Redis来实现TicketRegistry,我们使用Java调用接口Jedis来操作Redis,代码如下:

 

[html] view plaincopy
  1. import java.io.ByteArrayInputStream;  
  2. import java.io.ByteArrayOutputStream;  
  3. import java.io.ObjectInputStream;  
  4. import java.io.ObjectOutputStream;  
  5. import java.util.Collection;  
  6.   
  7. import org.jasig.cas.ticket.Ticket;  
  8. import org.jasig.cas.ticket.TicketGrantingTicket;  
  9. import org.jasig.cas.ticket.registry.AbstractDistributedTicketRegistry;  
  10.   
  11.   
  12. import redis.clients.jedis.Jedis;  
  13. import redis.clients.jedis.JedisPool;  
  14. import redis.clients.jedis.JedisPoolConfig;  
  15.   
  16.   
  17. /*  
  18.  *  TicketRegistry using Redis, to solve CAS Cluster.  
  19.  *    
  20.  *  @author ZL  
  21.  *   
  22.  */  
  23.   
  24. public class RedisTicketRegistry extends AbstractDistributedTicketRegistry {  
  25.   
  26.       
  27.     private static int redisDatabaseNum;  
  28.     private static String hosts;  
  29.     private static int port;  
  30.          private static int st_time;  //ST最大空闲时间  
  31.           private static int tgt_time; //TGT最大空闲时间  
  32.       
  33.     private static JedisPool cachePool;  
  34.       
  35.     static {  
  36.       
  37.         redisDatabaseNum = PropertiesConfigUtil.getPropertyInt("redis_database_num");  
  38.         hosts = PropertiesConfigUtil.getProperty("hosts");  
  39.         port = PropertiesConfigUtil.getPropertyInt("port");  
  40.         st_time = PropertiesConfigUtil.getPropertyInt("st_time");  
  41.         tgt_time = PropertiesConfigUtil.getPropertyInt("tgt_time");  
  42.         cachePool = new JedisPool(new JedisPoolConfig(), hosts, port);  
  43.           
  44.     }  
  45.       
  46.     public void addTicket(Ticket ticket) {  
  47.               
  48.         Jedis jedis = cachePool.getResource();  
  49.         jedis.select(redisDatabaseNum);  
  50.           
  51.                   int seconds = 0;  
  52.   
  53.                   String key = ticket.getId() ;  
  54.           
  55.         if(ticket instanceof TicketGrantingTicket){  
  56.             //key = ((TicketGrantingTicket)ticket).getAuthentication().getPrincipal().getId();  
  57.             seconds = tgt_time/1000;  
  58.         }else{  
  59.             seconds = st_time/1000;  
  60.         }  
  61.     
  62.           
  63.         ByteArrayOutputStream bos = new ByteArrayOutputStream();  
  64.         ObjectOutputStream oos = null;  
  65.         try{  
  66.             oos = new ObjectOutputStream(bos);  
  67.             oos.writeObject(ticket);  
  68.              
  69.         }catch(Exception e){  
  70.             log.error("adding ticket to redis error.");  
  71.         }finally{  
  72.             try{   
  73.                 if(null!=oos) oos.close();  
  74.             }catch(Exception e){  
  75.                 log.error("oos closing error when adding ticket to redis.");  
  76.             }  
  77.         }  
  78.         jedis.set(key.getBytes(), bos.toByteArray());  
  79.         jedis.expire(key.getBytes(), seconds);  
  80.           
  81.         cachePool.returnResource(jedis);  
  82.           
  83.     }  
  84.       
  85.     public Ticket getTicket(final String ticketId) {  
  86.         return getProxiedTicketInstance(getRawTicket(ticketId));  
  87.     }  
  88.       
  89.       
  90.     private Ticket getRawTicket(final String ticketId) {  
  91.           
  92.         if(null == ticketId) return null;  
  93.           
  94.         Jedis jedis = cachePool.getResource();  
  95.         jedis.select(redisDatabaseNum);  
  96.           
  97.         Ticket ticket = null;  
  98.           
  99.         ByteArrayInputStream bais = new ByteArrayInputStream(jedis.get(ticketId.getBytes()));  
  100.         ObjectInputStream ois = null;  
  101.           
  102.         try{  
  103.             ois = new ObjectInputStream(bais);  
  104.             ticket = (Ticket)ois.readObject();   
  105.         }catch(Exception e){  
  106.             log.error("getting ticket to redis error.");  
  107.         }finally{  
  108.             try{  
  109.                 if(null!=ois)  ois.close();  
  110.             }catch(Exception e){  
  111.                 log.error("ois closing error when getting ticket to redis.");  
  112.             }  
  113.         }  
  114.           
  115.         cachePool.returnResource(jedis);  
  116.           
  117.         return ticket;  
  118.     }  
  119.      
  120.       
  121.   
  122.     public boolean deleteTicket(final String ticketId) {  
  123.           
  124.         if (ticketId == null) {  
  125.             return false;  
  126.         }  
  127.           
  128.           
  129.         Jedis jedis = cachePool.getResource();  
  130.         jedis.select(redisDatabaseNum);  
  131.               
  132.         jedis.del(ticketId.getBytes());  
  133.           
  134.         cachePool.returnResource(jedis);  
  135.           
  136.         return true;          
  137.     }  
  138.    
  139.     public Collection<Ticket> getTickets() {  
  140.           
  141.         throw new UnsupportedOperationException("GetTickets not supported.");  
  142.   
  143.     }  
  144.   
  145.     protected boolean needsCallback() {  
  146.         return false;  
  147.     }  
  148.       
  149.     protected void updateTicket(final Ticket ticket) {  
  150.         addTicket(ticket);  
  151.     }  
  152.    
  153. }  


同时,我们在ticketRegistry.xml配置文件中,将TicketRegistry实现类指定为上述实现。即修改下面的class值

[html] view plaincopy
  1.     <!-- Ticket Registry -->  
  2.     <bean id="ticketRegistry" class="org.jasig.cas.util.RedisTicketRegistry" />  
  3.       
  4. <!--     <bean id="ticketRegistry" class="org.jasig.cas.ticket.registry.DefaultTicketRegistry" /> 
  5.  -->   

因为使用了Redis的expire功能,注释掉如下代码:

[html] view plaincopy
  1. <!-- TICKET REGISTRY CLEANER -->  
  2. lt;!--  <bean id="ticketRegistryCleaner" class="org.jasig.cas.ticket.registry.support.DefaultTicketRegistryCleaner"  
  3.     p:ticketRegistry-ref="ticketRegistry" />  
  4.   
  5. <bean id="jobDetailTicketRegistryCleaner" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"  
  6.     p:targetObject-ref="ticketRegistryCleaner"  
  7.     p:targetMethod="clean" />  
  8.   
  9. <bean id="triggerJobDetailTicketRegistryCleaner" class="org.springframework.scheduling.quartz.SimpleTriggerBean"  
  10.     p:jobDetail-ref="jobDetailTicketRegistryCleaner"  
  11.     p:startDelay="20000"  
  12.     p:repeatInterval="5000000" /> -->  


通过上述实现TicketRegistry,多台CAS服务器就可以共用同一个TicketRegistry。对于如何共享session,我们可以采用现成的第三方工具tomcat-redis-session-manager直接集成即可。对于前端web服务器(如nginx),做好负载均衡配置,将认证请求分布转发给后面多台CAS,实现负载均衡和容错目的。

 

 

 


原创粉丝点击