基於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,代碼如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
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值

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

 

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

?
1
2
3
4
5
6
7
8
9
10
11
12
<!-- 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,實現負載均衡和容錯目的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM