sso cas4.0改造历程--分布式部署实现

cas分布式部署改造实现 


前言

由于用户量的上升,项目运行中发现一台cas认证中心已经不足以满足需求。为此,需要做cas分布式改造。查询网上的各类资料,各种坑摆在那里。各种尝试以后,结合下面两篇文章。实现了cas的分布式部署。

所以,记录这篇博客。有需要的可以用,有不对的欢迎指正。


实现思路 

根据cas的原理,不细讲了(可以看参考文档)。实现cas的分布式部署,主要需要完成两块的内容实现。

1、采用统一的ticket存取策略,所有ticket的操作都从中央缓存redis中存取。

2、采用session共享,tomcat的session的存取都从中央缓存redis中存取。

3、通过nginx实现负载均衡

参考文档:

https://github.com/izerui/cas-ticket-redis

http://www.open-open.com/lib/view/open1363592675187.html


一、代码修改

1、在pom文件中添加三个jar包

<!-- redis -->
<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-redis</artifactId>
    <version>1.5.1.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    <version>2.2</version>
</dependency>
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.6.2</version>
</dependency>

2、新建类:RedisTicketRegistry  

    import org.jasig.cas.ticket.ServiceTicket;
    import org.jasig.cas.ticket.Ticket;
    import org.jasig.cas.ticket.TicketGrantingTicket;
    import org.jasig.cas.ticket.registry.AbstractDistributedTicketRegistry;
    import org.springframework.beans.factory.DisposableBean;

    import javax.validation.constraints.Min;
    import javax.validation.constraints.NotNull;
    import java.util.Collection;
    import java.util.HashSet;
    import java.util.Set;
    import java.util.concurrent.TimeUnit;

public final class RedisTicketRegistry extends AbstractDistributedTicketRegistry implements DisposableBean {

    private final static String TICKET_PREFIX = "TICKETGRANTINGTICKET:";

    /** redis client. */
    @NotNull
    private final TicketRedisTemplate client;

    /**
     * TGT cache entry timeout in seconds.
     */
    @Min(0)
    private final int tgtTimeout;

    /**
     * ST cache entry timeout in seconds.
     */
    @Min(0)
    private final int stTimeout;


    /**
     * Creates a new instance using the given redis client instance, which is presumably configured via
     * <code>net.spy.redis.spring.redisClientFactoryBean</code>.
     *
     * @param client                      redis client.
     * @param ticketGrantingTicketTimeOut TGT timeout in seconds.
     * @param serviceTicketTimeOut        ST timeout in seconds.
     */
    public RedisTicketRegistry(final TicketRedisTemplate client, final int ticketGrantingTicketTimeOut,
                               final int serviceTicketTimeOut) {
        this.tgtTimeout = ticketGrantingTicketTimeOut;
        this.stTimeout = serviceTicketTimeOut;
        this.client = client;
    }

    protected void updateTicket(final Ticket ticket) {
        logger.debug("Updating ticket {}", ticket);
        try {
            this.client.boundValueOps(TICKET_PREFIX+ticket.getId()).set(ticket,getTimeout(ticket), TimeUnit.SECONDS);
        } catch (final Exception e) {
            logger.error("Failed updating {}", ticket, e);
        }
    }

    public void addTicket(final Ticket ticket) {
        logger.debug("Adding ticket {}", ticket);
        try {
            this.client.boundValueOps(TICKET_PREFIX+ticket.getId()).set(ticket,getTimeout(ticket),TimeUnit.SECONDS);
        }catch (final Exception e) {
            logger.error("Failed adding {}", ticket, e);
        }
    }

    public boolean deleteTicket(final String ticketId) {
        logger.debug("Deleting ticket {}", ticketId);
        try {
            this.client.delete(TICKET_PREFIX+ticketId);
            return true;
        } catch (final Exception e) {
            logger.error("Failed deleting {}", ticketId, e);
        }
        return false;
    }

    public Ticket getTicket(final String ticketId) {
        try {
            final Ticket t = (Ticket) this.client.boundValueOps(TICKET_PREFIX+ticketId).get();
            if (t != null) {
                return getProxiedTicketInstance(t);
            }
        } catch (final Exception e) {
            logger.error("Failed fetching {} ", ticketId, e);
        }
        return null;
    }

    @Override
    public Collection<Ticket> getTickets() {
        Set<Ticket> tickets = new HashSet<Ticket>();
        Set<String> keys = this.client.keys(TICKET_PREFIX + "*");
        for (String key:keys){
            Ticket ticket = this.client.boundValueOps(TICKET_PREFIX+key).get();
            if(ticket==null){
                this.client.delete(TICKET_PREFIX+key);
            }else{
                tickets.add(ticket);
            }
        }
        return tickets;
    }

    public void destroy() throws Exception {
        //do nothing
    }

    /**
     * @param sync set to true, if updates to registry are to be synchronized
     * @deprecated As of version 3.5, this operation has no effect since async writes can cause registry consistency issues.
     */
    @Deprecated
    public void setSynchronizeUpdatesToRegistry(final boolean sync) {}

    @Override
    protected boolean needsCallback() {
        return true;
    }

    private int getTimeout(final Ticket t) {
        if (t instanceof TicketGrantingTicket) {
            return this.tgtTimeout;
        } else if (t instanceof ServiceTicket) {
            return this.stTimeout;
        }
        throw new IllegalArgumentException("Invalid ticket type");
    }
}

3、新建类TicketRedisTemplate

    import org.jasig.cas.ticket.Ticket;
    import org.springframework.data.redis.connection.RedisConnectionFactory;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
    import org.springframework.data.redis.serializer.RedisSerializer;
    import org.springframework.data.redis.serializer.StringRedisSerializer;

public class TicketRedisTemplate extends RedisTemplate<String, Ticket> {

    public TicketRedisTemplate() {
        RedisSerializer<String> string = new StringRedisSerializer();
        JdkSerializationRedisSerializer jdk = new JdkSerializationRedisSerializer();
        setKeySerializer(string);
        setValueSerializer(jdk);
        setHashKeySerializer(string);
        setHashValueSerializer(jdk);
    }

    public TicketRedisTemplate(RedisConnectionFactory connectionFactory) {
        this();
        setConnectionFactory(connectionFactory);
        afterPropertiesSet();
    }
}

4、在WEB-INF/spring-configuration/ticketRegistry.xml中修改ticketRegistry为我们自己新建的类。

 <!-- Ticket Registry -->
    <!--<bean id="ticketRegistry" class="org.jasig.cas.ticket.registry.DefaultTicketRegistry" />-->
	<bean id="ticketRegistry" class="com.xxx.sso.ticket.RedisTicketRegistry">
		<constructor-arg index="0" ref="redisTemplate" />

		<!-- TGT timeout in seconds -->
		<constructor-arg index="1" value="1800" />

		<!-- ST timeout in seconds -->
		<constructor-arg index="2" value="300" />
	</bean>

	<bean id="jedisConnFactory"
		  class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
		  p:hostName="xxxx"
		  p:database="0"
		  p:usePool="true"/>

	<bean id="redisTemplate" class="com.xxx.sso.ticket.TicketRedisTemplate"
		  p:connectionFactory-ref="jedisConnFactory"/>

其中“xxxx”为你自己存贮ticket的redis的ip

通过上述实现TicketRegistry,多台CAS服务器就可以共用同一个 TicketRegistry。


二、利用第三方工具tomcat-redis-session-manager实现,多台tomcat的session共享 

tomcat版本:7.0.61相关jar包

注意这里的jar包最好是按下面的版本,否则会出现jar包冲突的问题。(本人尝试多次最后找到的一组匹配版本)

tomcat-redis-session-manager-1.2-tomcat-7.jar

commons-pool-1.6.jar

jedis-2.1.0.jar

将以上3个jar包放入tomcat/lib目录中在tomcat context.xml中加入如下内容

<!host: optional: defaults to "localhost" -->
<Valve className="com.radiadesign.catalina.session.RedisSessionHandlerValve"/>
<Manager className="com.radiadesign.catalina.session.RedisSessionManager" host="192.168.1.245" port="6379" database="0" maxInactiveInterval="60" />


ps

如果使用 omcat-redis-session-manager出现:cas登出后,redis中的sessionid不会删除,并且st也继续保存。导致不关闭浏览器,再次登录,页面只顿删但是一直登陆不上的情况,则表示session共享不成功。本人尝试了修改cas源码,在登出时手动清除session,都不成功。
建议修改session共享方案。具体操作请查看该日志:nginx+tomcat+session共享



三、部署ngnix 

最终目的使最终发布的各台tomcat,使用同一个域名和端口号。不做赘述


四、验证 


先启动redis

然后启动部署在8080端口和7070端口的两个tomcat

再配置nginx,使80端口指向8080和7070端口最后,利用80端口访问login接口,登录成功以后。

在同一浏览器访问8080和7070的登录接口,直接显示登录成功。

调用登出接口,两台服务器调用登录接口都回到登录页面,需要重新登录。

至此,cas结合redis,nginx实现分布式部署就完成了。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值