CAS Client集群环境的Session问题及解决方案 不能退出登录
casclient源代码下载链接:https://github.com/apereo/java-cas-client
cas官网链接:https://www.apereo.org/projects/cas
1.上面一篇引用别人的分析方案介绍,来描述了下项目中遇到的问题,现在介绍本人怎么解决的
2.本人项目中用的是改造了tomcat 做的session 共享
3.所以客户端请求退出,服务端根据TGT查看对应的ST进行请求客户端,通过nginx负载均衡,可能对应到另一台客户端服务器,但是我们的session是存入rediscluster,任意客户端可以根据sessionid取到这个,进行删除,这样session就没了。即在客户端配置的SingleSignOutFilter,需要在HashMapBackedSessionMappingStorage进行删除存入redis的session数据,这样就可以退出了,就是这个原理来处理这个退出登录问题
一.上代码依赖jar包
spring-data-redis-1.7.4.RELEASE.jar;jedis-2.9.0.jar;fastjson-1.2.31.jar;注意jar包版本
二.改造的HashMapBackedSessionMappingStorage类代码
package org.jasig.cas.client.session; /* * 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. */ import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; import javax.annotation.Resource; import javax.servlet.http.HttpSession; import org.jasig.cas.client.session.SessionMappingStorage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.EnableMBeanExport; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; /** * HashMap backed implementation of SessionMappingStorage. * * @author Scott Battaglia * @version $Revision$ $Date$ * @since 3.1 * */ public final class HashMapBackedSessionMappingStorage implements SessionMappingStorage { protected final transient Logger logger = LoggerFactory.getLogger(getClass()); private final static String CASCLIENT_PREFIX = "CASCLI:SESSIONID:"; private final static String CASCLIENT_MAPID_PREFIX = "CASCLI:MAPID:"; private int casTimeout=86400; private RedisTemplate redisTemplate=new RedisTemplate(); public RedisTemplate getRedisTemplate() { return redisTemplate; } public void setRedisTemplate(RedisTemplate redisTemplate) { this.redisTemplate = redisTemplate; } public HashMapBackedSessionMappingStorage(){ ApplicationContext ac =new ClassPathXmlApplicationContext("classpath:schemeone/xml/spring-core.xml"); setRedisTemplate((RedisTemplate)ac.getBean("redisTemplate")); } @Override public synchronized void addSessionById(String mappingId, HttpSession session) { logger.debug("Adding ticket {}", session); try { String sessionRedisKey = this.getCasSessionRedisKey(session.getId()); String mappingIdRedisKey = this.getCasMappingIdRedisKey(mappingId); this.redisTemplate.boundValueOps(sessionRedisKey).set(mappingId, casTimeout, TimeUnit.SECONDS); this.redisTemplate.boundValueOps(mappingIdRedisKey).set(session.getId(), casTimeout, TimeUnit.SECONDS); } catch (final Exception e) { logger.error("Failed Adding {}", session, e); } } @Override public synchronized void removeBySessionById(String sessionId) { logger.debug("Attempting to remove Session=[{}]", sessionId); final String key =(String) this.redisTemplate.boundValueOps(this.getCasSessionRedisKey(sessionId)).get(); if (logger.isDebugEnabled()) { if (key != null) { logger.debug("Found mapping for session. Session Removed."); } else { logger.debug("No mapping for session found. Ignoring."); } } this.redisTemplate.delete(this.getCasMappingIdRedisKey(key)); this.redisTemplate.delete(this.getCasSessionRedisKey(sessionId)); } @Override public synchronized HttpSession removeSessionByMappingId(String mappingId) { //先去取sessionid final String sessionId=(String) this.redisTemplate.boundValueOps(this.getCasMappingIdRedisKey(mappingId)).get(); //final HttpSession session = (HttpSession) this.redisTemplate.boundValueOps(sessionId).get(); this.redisTemplate.delete(sessionId); // if (session != null) { // removeBySessionById(session.getId()); // } // return session; if (sessionId != null) { removeBySessionById(sessionId); } return null; } private String getCasSessionRedisKey( String sessionId) { return CASCLIENT_PREFIX + sessionId; } private String getCasMappingIdRedisKey(String mappingId) { return CASCLIENT_MAPID_PREFIX + mappingId; } }
三。redistemplteBean配置
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"> <!-- 扫描注解Bean --> <context:component-scan base-package="com.hivescm" /> <aop:config proxy-target-class="true" /> <!-- 开启AOP监听 只对当前配置文件有效 --> <aop:aspectj-autoproxy expose-proxy="true" /> <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations"> <list> <!-- <value>classpath:schemeone/properties/common/*.properties</value> --> <value>classpath:schemeone/properties/common/redis.cluster.properties</value> </list> </property> </bean> <!-- jedis 配置 --> <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig" > <!-- 最大空闲数 --> <property name="maxIdle" value="${redis.maxIdle}" /> <!-- 最大建立连接等待时间 --> <property name="maxWaitMillis" value="${redis.maxWait}" /> <!-- 是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个 --> <property name="testOnBorrow" value="${redis.testOnBorrow}" /> </bean> <!-- 配置文件加载 --> <bean id="resourcePropertySource" class="org.springframework.core.io.support.ResourcePropertySource"> <constructor-arg name="name" value="redis.cluster.properties"/> <constructor-arg name="resource" value="classpath:schemeone/properties/common/redis.cluster.properties"/> </bean> <!-- redisCluster配置 --> <bean id="redisClusterConfiguration" class="org.springframework.data.redis.connection.RedisClusterConfiguration"> <constructor-arg name="propertySource" ref="resourcePropertySource"/> </bean> <!-- redis服务器中心 --> <bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" > <constructor-arg name="clusterConfig" ref="redisClusterConfiguration"/> <constructor-arg name="poolConfig" ref="poolConfig"/> <property name="password" value="${redis.password}" /> <property name="timeout" value="${redis.timeout}" ></property> </bean > <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate" > <property name="connectionFactory" ref="connectionFactory" /> <!-- 如果不配置Serializer,那么存储的时候缺省使用String,如果用User类型存储,那么会提示错误User can't cast to String!! --> <property name="keySerializer" > <bean class="org.springframework.data.redis.serializer.StringRedisSerializer" /> </property> <property name="valueSerializer" > <bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" /> </property> <property name="hashKeySerializer"> <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/> </property> <property name="hashValueSerializer"> <bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"/> </property> </bean > </beans>
4.redis.cluster.properties
#redis\u4E2D\u5FC3 #redis\u7684\u670D\u52A1\u5668\u5730\u5740 redis.host=192.168.103.158 #redis\u7684\u670D\u52A1\u7AEF\u53E3 redis.port=6379 #\u5BC6\u7801 redis.password= #\u6700\u5927\u7A7A\u95F2\u6570 redis.maxIdle=100 #\u6700\u5927\u8FDE\u63A5\u6570 redis.maxActive=300 #\u6700\u5927\u5EFA\u7ACB\u8FDE\u63A5\u7B49\u5F85\u65F6\u95F4 redis.maxWait=1000 #\u5BA2\u6237\u7AEF\u8D85\u65F6\u65F6\u95F4\u5355\u4F4D\u662F\u6BEB\u79D2 redis.timeout=100000 redis.maxTotal=1000 redis.minIdle=8 #\u660E\u662F\u5426\u5728\u4ECE\u6C60\u4E2D\u53D6\u51FA\u8FDE\u63A5\u524D\u8FDB\u884C\u68C0\u9A8C,\u5982\u679C\u68C0\u9A8C\u5931\u8D25,\u5219\u4ECE\u6C60\u4E2D\u53BB\u9664\u8FDE\u63A5\u5E76\u5C1D\u8BD5\u53D6\u51FA\u53E6\u4E00\u4E2A redis.testOnBorrow=true #sentinel #spring.redis.sentinel.node1.host=127.0.0.1 #spring.redis.sentinel.node2.host=127.0.0.1 #spring.redis.sentinel.node3.host=127.0.0.1 #spring.redis.sentinel.node1.port=26379 #spring.redis.sentinel.node2.port=26479 #spring.redis.sentinel.node3.port=26579 #sentinel #jediscluster #cluster1.host.port=127.0.0.1:7000 #cluster2.host.port=127.0.0.1:7001 #cluster3.host.port=127.0.0.1:7002 #cluster4.host.port=127.0.0.1:7003 #cluster5.host.port=127.0.0.1:7004 #cluster6.host.port=127.0.0.1:7005 #cluster7.host.port=127.0.0.1:7006 #cluster8.host.port=127.0.0.1:7007 #jediscluster #rediscluster #spring.redis.cluster.nodes=192.168.103.158:6379 spring.redis.cluster.nodes=192.168.103.174:6379,192.168.103.174:6389,192.168.103.174:6399,192.168.103.173:6379,192.168.103.173:6389,192.168.103.173:6399 spring.redis.cluster.max-redirects=3