Client Server集群模式下session问题[转]
当我们CAS Server准备好后,就要处理Client接入的问题,如果我们的Client服务是单机模式那没有任何问题,一旦放到集群环境下就会发生如下有意思的事情。
我前面说了CAS在授权回调时会做几件事,第一TG保存到Cookie,第二个保存ticketid对应的session关系以及session对象。
那么如果我们的Client服务是集群的会发生什么?
举个例子:
我的APP服务部署了2台服务(S1、S2)采用loadbalance映射一个域名出去访问,当CAS授权回调时被loadbalance路由到S1上,SingleSignOutFilter以及SingleSignOutHandler进行了TGC和SessionMappingStorage,默认的持久化方式是hash的方式,也就是说本地map方式,这样在下次访问到APP时被loadbalance路由到S2上就会发生什么有意思的事情呢?我相信做过分布式服务的应该都能猜出来什么问题。
APP:我没找到cas认证信息,跳转到cas login页面
CAS:我找到了你APP已经做过认证了,跳转到APP并且给你上次认证的ticlet
APP:我真没找到你的认证信息,跳转到cas login页面
CAS:你真的已经做过认证了,跳转到APP并且给你上次认证的ticlet
这样就会发生无线跳转死循环问题。
那如何解决上面的问题呢?
在分布式的环境下几乎服务都是集群的,甚至有很多公司会做异地多活等等。那么在集群环境下如何解决cas授权持久化的问题呢?很简单重新实现一个cas-client的SessionMappingStorage,这里可以使用很多方式,比如说:放到db、nosql的存储上(mongodb、redis)、memcache、分布式文件存储都可以。
我这里采用的是redis,而且我们dev和qa环境采用单机模式,stage和prod环境使用集群模式,因此我还做了集群和本地都兼容的方式,话不多说直接贴出实现代码
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpSession;
import org.jasig.cas.client.session.SessionMappingStorage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import xxxxxxxx.framework.redis.client.IRedisClient;
public class RedisBackedSessionMappingStorage implements SessionMappingStorage {
private final Logger logger = LoggerFactory.getLogger(getClass());
/**
* Maps the ID from the CAS server to the Session.
*/
private final Map<String, HttpSession> MANAGED_SESSIONS = new HashMap<String, HttpSession>();
/**
* Maps the Session ID to the key from the CAS Server.
*/
private final Map<String, String> ID_TO_SESSION_KEY_MAPPING = new HashMap<String, String>();
private final static String NAME_SPACE = "CAS";
private IRedisClient redisClient;
/**
* 在dev和qa环境使用单机模式:hash
* 在stage和prod环境使用集群模式:redis
*/
private String storageMode = "hash";
/**
* 获取 redisClient
* @return the redisClient
*/
public IRedisClient getRedisClient() {
return redisClient;
}
/**
* 设置 redisClient
* @param redisClient the redisClient to set
*/
public void setRedisClient(IRedisClient redisClient) {
this.redisClient = redisClient;
}
/**
* 获取 storageMode
* @return the storageMode
*/
public String getStorageMode() {
return storageMode;
}
/**
* 设置 storageMode
* @param storageMode the storageMode to set
*/
public void setStorageMode(String storageMode) {
this.storageMode = storageMode;
}
@Override
public HttpSession removeSessionByMappingId(String mappingId) {
HttpSession session = null;
if (storageMode.equals("hash")) {
session = MANAGED_SESSIONS.get(mappingId);
} else {
session = redisClient.get(mappingId, NAME_SPACE, HttpSession.class, null);
}
if (session != null) {
removeBySessionById(session.getId());
}
return session;
}
@Override
public void removeBySessionById(String sessionId) {
logger.debug("Attempting to remove Session=[{}]", sessionId);
String key = null;
if (storageMode.equals("hash")) {
key = ID_TO_SESSION_KEY_MAPPING.get(sessionId);
} else {
key = redisClient.get(sessionId, NAME_SPACE, null);
}
if (logger.isDebugEnabled()) {
if (key != null) {
logger.debug("Found mapping for session. Session Removed.");
} else {
logger.debug("No mapping for session found. Ignoring.");
}
}
if (storageMode.equals("hash")) {
MANAGED_SESSIONS.remove(key);
ID_TO_SESSION_KEY_MAPPING.remove(sessionId);
} else {
redisClient.del(key, NAME_SPACE);
redisClient.del(sessionId, NAME_SPACE);
}
}
@Override
public void addSessionById(String mappingId, HttpSession session) {
if (storageMode.equals("hash")) {
ID_TO_SESSION_KEY_MAPPING.put(session.getId(), mappingId);
MANAGED_SESSIONS.put(mappingId, session);
} else {
redisClient.set(session.getId(), NAME_SPACE, mappingId, -1);
redisClient.set(mappingId, NAME_SPACE, session, -1);
}
}
}
这里使用的redis-client是我自己封装,使用文档在:《RedisClient使用说明》,支持redis集群模式:《RedisClient升级支持Sentinel使用说明》,代码已经放到了github上:
项目地址:redis-client
把上面的RedisBackedSessionMappingStorage类注入到org.jasig.cas.client.session.SingleSignOutFilter中即可
<bean id="singleLogoutFilter" class="org.jasig.cas.client.session.SingleSignOutFilter"> <property name="sessionMappingStorage" ref="redisBackedSessionMappingStorage"></property> </bean> <bean id="redisBackedSessionMappingStorage" class="xxxxxxx.cas.session.storage.RedisBackedSessionMappingStorage"> <property name="redisClient" ref="redisClient"></property> <property name="storageMode" value="${cas.session.storage.mode}"></property> </bean>
ps.参数cas.session.storage.mode,值:hash(本地map)、redis(集中存储)
- 本文作者: 凝雨-Yun
- 本文标题: CAS使用经验总结,纯干货
- 本文链接: https://ningyu1.github.io/site/post/54-cas-server/
- 发布时间:2018-01-19
- 版权声明: 本文由 凝雨-Yun 原创,采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。
转载请保留以上声明信息!

浙公网安备 33010602011771号