cas单点登录集群如何优雅的退出

实现cas ticket基于redis的集群

目的

克服cas单点故障,将cas认证请求分发到多台cas服务器上,降低负载。 实现思路:

采用统一的ticket存取策略,所有ticket的操作都从中央缓存redis中存取。 采用session共享,session的存取都从中央缓存redis中存取。 前提:

这里只讲解如何实现cas ticket的共享,关于session的共享请移步: https://github.com/izerui/tomcat-redis-session-manager 实现步骤:

基于cas源码 新增模块 cas-server-integration-redis

pom.xml 文件如下:

<?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>

<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 包下

RedisTicketRegistry.java

/*

  • Licensed to Jasig under one or more contributor license
  • agreements. See the NOTICE file distributed with this work
  • for additional information regarding copyright ownership.
  • Jasig licenses this file to you under the Apache License,
  • Version 2.0 (the "License"); you may not use this file
  • except in compliance with the License. You may obtain a
  • copy of the License at the following location:
  • http://www.apache.org/licenses/LICENSE-2.0
  • Unless required by applicable law or agreed to in writing,
  • software distributed under the License is distributed on an
  • "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  • KIND, either express or implied. See the License for the
  • specific language governing permissions and limitations
  • under the License. */ package org.jasig.cas.ticket.registry;

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;

    /**

    • 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; }

    /**

    • {@inheritDoc}
    • This operation is not supported.
    • @throws UnsupportedOperationException if you try and call this operation. / @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"); } } 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存储即可生效

转载于:https://my.oschina.net/heinrichchen/blog/715071

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值