實現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存儲便可生效