基于Redis的CAS集群

单点登录(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 plain   copy  

  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 plain   copy  

  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 plain   copy  

  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,实现负载均衡和容错目的。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值