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
三、部署ngnix
最终目的使最终发布的各台tomcat,使用同一个域名和端口号。不做赘述
四、验证
先启动redis
然后启动部署在8080端口和7070端口的两个tomcat
再配置nginx,使80端口指向8080和7070端口最后,利用80端口访问login接口,登录成功以后。
在同一浏览器访问8080和7070的登录接口,直接显示登录成功。
调用登出接口,两台服务器调用登录接口都回到登录页面,需要重新登录。
至此,cas结合redis,nginx实现分布式部署就完成了。