实现cas ticket基于redis的集群java
目的nginx
克服cas单点故障,将cas认证请求分发到多台cas服务器上,下降负载。 实现思路:git
采用统一的ticket存取策略,全部ticket的操做都从中央缓存redis中存取。 采用session共享,session的存取都从中央缓存redis中存取。 前提:github
这里只讲解如何实现cas ticket的共享,关于session的共享请移步: https://github.com/izerui/tomcat-redis-session-manager 实现步骤:web
基于cas源码 新增模块 cas-server-integration-redisredis
pom.xml 文件以下:spring
<?xml version="1.0" encoding="UTF-8"?>数据库
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>cas-server</artifactId> <groupId>org.jasig.cas</groupId> <version>4.0.2</version> </parent> <modelVersion>4.0.0</modelVersion>express
<artifactId>cas-server-integration-redis</artifactId> <dependencies> <dependency> <groupId>org.jasig.cas</groupId> <artifactId>cas-server-core</artifactId> <version>${project.version}</version> </dependency> <dependency> <groupId>org.jasig.cas</groupId> <artifactId>cas-server-support-saml</artifactId> <version>${project.version}</version> <scope>provided</scope> </dependency> <!-- 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> </dependencies>
</project> 添加类到 cas-server-integration-redis 模块的 org.jasig.cas.ticket.registry 包下apache
RedisTicketRegistry.java
/*
import org.jasig.cas.ticket.ServiceTicket; import org.jasig.cas.ticket.Ticket; import org.jasig.cas.ticket.TicketGrantingTicket; 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;
/**
Key-value ticket registry implementation that stores tickets in redis keyed on the ticket ID.
[@author](http://my.oschina.net/arthor) Scott Battaglia
[@author](http://my.oschina.net/arthor) Marvin S. Addison
[@since](http://my.oschina.net/u/266547) 3.3 */ public final class RedisTicketRegistry extends AbstractDistributedTicketRegistry implements DisposableBean {
private final static String TICKET_PREFIX = "TICKETGRANTINGTICKET:";
/** redis client. */ [@NotNull](http://my.oschina.net/notnull) private final TicketRedisTemplate 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; }
/**
public void destroy() throws Exception { //do nothing }
/**
@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"); } } TicketRedisTemplate.java
package org.jasig.cas.ticket.registry;
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;
/**
Created by serv on 2015/7/19. */ 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(); } } cas-server-webapp 添加刚才新增模块的依赖
<dependency> <groupId>org.jasig.cas</groupId> <artifactId>cas-server-integration-redis</artifactId> <version>${project.version}</version> </dependency> 修改 spring配置文件 cas-server-webapp 模块 WEB-INF\spring-configuration\ticketRegistry.xml
<bean id="ticketRegistry" class="org.jasig.cas.ticket.registry.DefaultTicketRegistry" /> 替换为
<bean id="ticketRegistry" class="org.jasig.cas.ticket.registry.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="192.168.1.89" p:database="0" p:usePool="true"/>
<bean id="redisTemplate" class="org.jasig.cas.ticket.registry.TicketRedisTemplate" p:connectionFactory-ref="jedisConnFactory"/> 注意: 里面的 jedisConnFactory连接信息 修改成本身的链接串,这里选择database 1为存放cas票据的数据库
从新编译 mvn install
生成的cas.war 部署到多个已经作过session共享的tomcat容器中。
tomcat-redis-session-manager
使用redis配置tomcat共享session 结构图:
分析:
分布式web server集群部署后须要实现session共享,针对 tomcat 服务器的实现方案多种多样, 好比 tomcat cluster session 广播、nginx IP hash策略、nginx sticky module等方案, 本文主要介绍了使用 redis 服务器进行 session 统一存储管理的共享方案。 必要环境:
java1.7 tomcat7 redis2.8 nginx 负载均衡配置
修改nginx conf配置文件加入
upstream tomcat { server 200.10.10.67:8110; server 200.10.10.67:8120; server 200.10.10.44:8110; server 200.10.10.66:8110; } 配置 相应的server或者 location地址到 http://tomcat
tomcat session共享配置步骤
添加redis session集群依赖的jar包到 TOMCAT_BASE/lib 目录下
tomcat-redis-session-manager-2.0.0.jar jedis-2.5.2.jar commons-pool2-2.2.jar 修改 TOMCAT_BASE/conf 目录下的 context.xml 文件
<Valve className="com.orangefunction.tomcat.redissessions.RedisSessionHandlerValve" /> <Manager className="com.orangefunction.tomcat.redissessions.RedisSessionManager" host="localhost" port="6379" database="0" maxInactiveInterval="60" sessionPersistPolicies="PERSIST_POLICY_1,PERSIST_POLICY_2,.." sentinelMaster="SentinelMasterName" sentinels="sentinel-host-1:port,sentinel-host-2:port,.."/>
属性解释:
host redis服务器地址 port redis服务器的端口号 database 要使用的redis数据库索引 maxInactiveInterval session最大空闲超时时间,若是不填则使用tomcat的超时时长,通常tomcat默认为1800 即半个小时 sessionPersistPolicies session保存策略,除了默认的策略还能够选择的策略有:
[SAVE_ON_CHANGE]:每次 session.setAttribute() 、 session.removeAttribute() 触发都会保存. 注意:此功能没法检测已经存在redis的特定属性的变化, 权衡:这种策略会略微下降会话的性能,任何改变都会保存到redis中.
注意:对于更改一个已经存储在redis中的会话属性,该选项特别有用. 权衡:若是不是全部的request请求都要求改变会话属性的话不推荐使用,由于会增长并发竞争的状况。 sentinelMaster redis集群主节点名称(Redis集群是以分片(Sharding)加主从的方式搭建,知足可扩展性的要求) sentinels redis集群列表配置(相似zookeeper,经过多个Sentinel来提升系统的可用性) connectionPoolMaxTotal connectionPoolMaxIdle jedis最大可以保持idel状态的链接数 connectionPoolMinIdle 与connectionPoolMaxIdle相反 maxWaitMillis jedis池没有对象返回时,最大等待时间 minEvictableIdleTimeMillis softMinEvictableIdleTimeMillis numTestsPerEvictionRun testOnCreate testOnBorrow jedis调用borrowObject方法时,是否进行有效检查 testOnReturn jedis调用returnObject方法时,是否进行有效检查 testWhileIdle timeBetweenEvictionRunsMillis evictionPolicyClassName blockWhenExhausted jmxEnabled jmxNameBase jmxNamePrefix * **** 重启tomcat,session存储便可生效