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

基于Redis的CAS集群的更多相关文章

  1. 基于redis的cas集群配置(转)

    1.cas ticket统一存储 做cas集群首先需要将ticket拿出来,做统一存储,以便每个节点访问到的数据一致.官方提供基于memcached的方案,由于项目需要,需要做计入redis,根据官方 ...

  2. 基于redis的cas集群配置

    1.cas ticket统一存储 做cas集群首先需要将ticket拿出来,做统一存储,以便每个节点访问到的数据一致.官方提供基于memcached的方案,由于项目需要,需要做计入redis,根据官方 ...

  3. 基于Redis的CAS服务端集群

    为了保证生产环境CAS(Central Authentication Service)认证服务的高可用,防止出现单点故障,我们需要对CAS Server进行集群部署. CAS的Ticket默认是以Ma ...

  4. 基于docker实现redis高可用集群

    基于docker实现redis高可用集群 yls 2019-9-20 简介 基于docker和docker-compose 使用redis集群和sentinel集群,达到redis高可用,为缓存做铺垫 ...

  5. CAS 集群部署session共享配置

    背景 前段时间,项目计划搞独立的登录鉴权中心,由于单独开发一套稳定的登录.鉴权代码,工作量大,最终的方案是对开源鉴权中心CAS(Central Authentication Service)作适配修改 ...

  6. Redis+Twemproxy+HAProxy集群(转) 干货

    原文地址:Redis+Twemproxy+HAProxy集群  干货 Redis主从模式 Redis数据库与传统数据库属于并行关系,也就是说传统的关系型数据库保存的是结构化数据,而Redis保存的是一 ...

  7. Redis高可用集群-哨兵模式(Redis-Sentinel)搭建配置教程【Windows环境】

    No cross,no crown . 不经历风雨,怎么见彩虹. Redis哨兵模式,用现在流行的话可以说就是一个"哨兵机器人",给"哨兵机器人"进行相应的配置 ...

  8. redis 3.0 集群__数据迁移和伸缩容

    添加节点 1,启动2个新的redis-sever, 参照 ( redis 3.0 集群____安装 ),端口号为 7007 和 7008 2,使用命令 redis-trib.rb add-node 命 ...

  9. redis主从、集群、哨兵

    redis的主从.集群.哨兵 参考: https://blog.csdn.net/robertohuang/article/details/70741575 https://blog.csdn.net ...

随机推荐

  1. String类为什么设计成不可变的

    在Java中将String设计成不可变的是综合考虑到各种因素的结果,需要综合考虑内存.同步.数据结构以安全方面的考虑. String被设计成不可变的主要目的是为了安全和高效. 1)字符串常量池的需要 ...

  2. Snownlp

    from snownlp import SnowNLP text='宝贝自拍很帅!!!注意休息-' s=SnowNLP(text) #分词print(s.words) #词性for tag in s. ...

  3. java面试感悟【一】

    我最终选择不包装工作经验,或许是因为我怂,或许是因为一些莫名其妙的坚持…… 然而结果就是在boss上沟通了20多家,只有7家让我投了简历,1家跟我说要我发个时间段给他稍后告诉我面试时间,然后就没有然后 ...

  4. 转载 html div三列布局占满全屏(左右两列定宽或者百分比、中间自动适应,div在父div中居底)

    原文地址:http://blog.csdn.net/duyelang/article/details/20558899 <p><!DOCTYPE html> <html ...

  5. Proxmox Reset Root Password

    http://c-nergy.be/blog/?p=1777 Step 1 – Boot your Proxmox VE machine. In the boot menu screen, you s ...

  6. Python导入自定义类时显示错误:attempted relative import beyond top-level package

    显示这个错误可能有两个原因: 1.文件夹中没有包含__init__.py文件,该文件可以为空,但必须存在该文件. 2.把该文件当成主函数入口,该文件所在文件夹不能被解释器视作package,所以可能导 ...

  7. css初识和css选择器

    一.css是什么 css(cascading style sheet)定义如何显示HTML元素,给HTML设置样式,显得更为美观. 二.css的引入方式 1.行内引入 在标签中添加一个style是属性 ...

  8. fiddler抓包时显示Tunnel to......443是怎么回事

    之前公司的app使用的http协议,因此不需要安装证书也能够转包. 后来改成https协议后,在使用fiddler进行抓包时,一直出现tunnel to 443. 百度了好久也没有具体的解决办法,后来 ...

  9. 2018.10.27 codeforces402D. Upgrading Array(数论+贪心)

    传送门 唉我觉得这题数据范围1e5都能做啊... 居然只出了2000 考完听zxyzxyzxy说我的贪心可以卡但过了? 可能今天本来是0+10+00+10+00+10+0只是运气好T1T1T1骗了10 ...

  10. ACM-ICPC 2018 徐州赛区网络预赛 G Trace(逆向,两颗线段树写法)

    https://nanti.jisuanke.com/t/31459 思路 凡是后面的轨迹对前面的轨迹有影响的,可以尝试从后往前扫 区间修改需要push_down,单点更新所以不需要push_up(用 ...