基于Redis的CAS集群

jopen 11年前發布 | 108K 次閱讀 Redis OpenID/單點登錄SSO

單點登錄(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,代碼如下:

    import java.io.ByteArrayInputStream;  
    import java.io.ByteArrayOutputStream;  
    import java.io.ObjectInputStream;  
    import java.io.ObjectOutputStream;  
    import java.util.Collection;  

    import org.jasig.cas.ticket.Ticket;  
    import org.jasig.cas.ticket.TicketGrantingTicket;  
    import org.jasig.cas.ticket.registry.AbstractDistributedTicketRegistry;  


    import redis.clients.jedis.Jedis;  
    import redis.clients.jedis.JedisPool;  
    import redis.clients.jedis.JedisPoolConfig;  


    /*  
     *  TicketRegistry using Redis, to solve CAS Cluster.  
     *    
     *  @author ZL  
     *   
     */  

    public class RedisTicketRegistry extends AbstractDistributedTicketRegistry {  


        private static int redisDatabaseNum;  
        private static String hosts;  
        private static int port;  
             private static int st_time;  //ST最大空閑時間  
              private static int tgt_time; //TGT最大空閑時間  

        private static JedisPool cachePool;  

        static {  

            redisDatabaseNum = PropertiesConfigUtil.getPropertyInt("redis_database_num");  
            hosts = PropertiesConfigUtil.getProperty("hosts");  
            port = PropertiesConfigUtil.getPropertyInt("port");  
            st_time = PropertiesConfigUtil.getPropertyInt("st_time");  
            tgt_time = PropertiesConfigUtil.getPropertyInt("tgt_time");  
            cachePool = new JedisPool(new JedisPoolConfig(), hosts, port);  

        }  

        public void addTicket(Ticket ticket) {  

            Jedis jedis = cachePool.getResource();  
            jedis.select(redisDatabaseNum);  

                      int seconds = 0;  

                      String key = ticket.getId() ;  

            if(ticket instanceof TicketGrantingTicket){  
                //key = ((TicketGrantingTicket)ticket).getAuthentication().getPrincipal().getId();  
                seconds = tgt_time/1000;  
            }else{  
                seconds = st_time/1000;  
            }  


            ByteArrayOutputStream bos = new ByteArrayOutputStream();  
            ObjectOutputStream oos = null;  
            try{  
                oos = new ObjectOutputStream(bos);  
                oos.writeObject(ticket);  

            }catch(Exception e){  
                log.error("adding ticket to redis error.");  
            }finally{  
                try{   
                    if(null!=oos) oos.close();  
                }catch(Exception e){  
                    log.error("oos closing error when adding ticket to redis.");  
                }  
            }  
            jedis.set(key.getBytes(), bos.toByteArray());  
            jedis.expire(key.getBytes(), seconds);  

            cachePool.returnResource(jedis);  

        }  

        public Ticket getTicket(final String ticketId) {  
            return getProxiedTicketInstance(getRawTicket(ticketId));  
        }  


        private Ticket getRawTicket(final String ticketId) {  

            if(null == ticketId) return null;  

            Jedis jedis = cachePool.getResource();  
            jedis.select(redisDatabaseNum);  

            Ticket ticket = null;  

            ByteArrayInputStream bais = new ByteArrayInputStream(jedis.get(ticketId.getBytes()));  
            ObjectInputStream ois = null;  

            try{  
                ois = new ObjectInputStream(bais);  
                ticket = (Ticket)ois.readObject();   
            }catch(Exception e){  
                log.error("getting ticket to redis error.");  
            }finally{  
                try{  
                    if(null!=ois)  ois.close();  
                }catch(Exception e){  
                    log.error("ois closing error when getting ticket to redis.");  
                }  
            }  

            cachePool.returnResource(jedis);  

            return ticket;  
        }  



        public boolean deleteTicket(final String ticketId) {  

            if (ticketId == null) {  
                return false;  
            }  


            Jedis jedis = cachePool.getResource();  
            jedis.select(redisDatabaseNum);  

            jedis.del(ticketId.getBytes());  

            cachePool.returnResource(jedis);  

            return true;          
        }  

        public Collection<Ticket> getTickets() {  

            throw new UnsupportedOperationException("GetTickets not supported.");  

        }  

        protected boolean needsCallback() {  
            return false;  
        }  

        protected void updateTicket(final Ticket ticket) {  
            addTicket(ticket);  
        }  

    }  
同時,我們在ticketRegistry.xml配置文件中,將TicketRegistry實現類指定為上述實現。即修改下面的class值
    <!-- Ticket Registry -->
    <bean id="ticketRegistry" class="org.jasig.cas.util.RedisTicketRegistry" />

<!--     <bean id="ticketRegistry" class="org.jasig.cas.ticket.registry.DefaultTicketRegistry" />
 --> 

因為使用了Redis的expire功能,注釋掉如下代碼:

    <!-- TICKET REGISTRY CLEANER -->  
    lt;!--  <bean id="ticketRegistryCleaner" class="org.jasig.cas.ticket.registry.support.DefaultTicketRegistryCleaner"  
        p:ticketRegistry-ref="ticketRegistry" />  

    <bean id="jobDetailTicketRegistryCleaner" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"  
        p:targetObject-ref="ticketRegistryCleaner"  
        p:targetMethod="clean" />  

    <bean id="triggerJobDetailTicketRegistryCleaner" class="org.springframework.scheduling.quartz.SimpleTriggerBean"  
        p:jobDetail-ref="jobDetailTicketRegistryCleaner"  
        p:startDelay="20000"  
        p:repeatInterval="5000000" /> -->  
通過上述實現TicketRegistry,多臺CAS服務器就可以共用同一個 TicketRegistry。對于如何共享session,我們可以采用現成的第三方工具tomcat-redis-session-manager直接集成即可。對于前端web服務器(如nginx),做好負載均衡配置,將認證請求分布轉發給后面多臺CAS,實現負載均衡和容錯目的。

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!