最近在线上出现了,退出一个客户端后,另外一个客户端会出现退出失败的问题,一开始觉得这个问题是服务端的问题,于是查询log发现,服务是发送了退出通知的,于是去查询客户端的log后,发现原来客户端接收到退出通知后,有session就会销毁然后退出,没有的话也不会继续通知其他的集群下的客户端,导致了集群下的客户端退出失败,于是查询网上的资料,有2中解决办法
1、采用广播式的方式去通知,假如通知的客户端没有session就会继续通知其他集群地址下的客户端,直到全部销毁
2、采用spring data redis 的方式存储客户端session的方式,接收到退出通知的消息后,直接删除销毁redis中的session
由于客户端项目比较古老了,采用了第一种方式,广播式的方法进行,修改源码,下面上代码
SingleSignOutHandler
public final class SingleSignOutHandler {
private static final int DECOMPRESSION_FACTOR = 10;
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private SessionMappingStorage sessionMappingStorage = new HashMapBackedSessionMappingStorage();
private String artifactParameterName;
private String logoutParameterName;
private String relayStateParameterName;
private String casServerUrlPrefix;
private String logoutCallbackPath;
private boolean artifactParameterOverPost;
private boolean eagerlyCreateSessions;
private List<String> safeParameters;
private final SingleSignOutHandler.LogoutStrategy logoutStrategy;
private String logoutParameterClusterName = "logoutRequestCluster";
///clusterNodeUrls
private String clusterNodeUrls = PropertiesUtil.getDomainName("clusterNodeUrls");
public SingleSignOutHandler() {
this.artifactParameterName = Protocol.CAS2.getArtifactParameterName();
this.logoutParameterName = (String)ConfigurationKeys.LOGOUT_PARAMETER_NAME.getDefaultValue();
this.relayStateParameterName = (String)ConfigurationKeys.RELAY_STATE_PARAMETER_NAME.getDefaultValue();
this.casServerUrlPrefix = "";
this.artifactParameterOverPost = false;
this.eagerlyCreateSessions = true;
this.logoutStrategy = (SingleSignOutHandler.LogoutStrategy)(isServlet30() ? new SingleSignOutHandler.Servlet30LogoutStrategy() : new SingleSignOutHandler.Servlet25LogoutStrategy());
}
public void setSessionMappingStorage(SessionMappingStorage storage) {
this.sessionMappingStorage = storage;
}
public void setArtifactParameterOverPost(boolean artifactParameterOverPost) {
this.artifactParameterOverPost = artifactParameterOverPost;
}
public SessionMappingStorage getSessionMappingStorage() {
return this.sessionMappingStorage;
}
public void setArtifactParameterName(String name) {
this.artifactParameterName = name;
}
public void setLogoutParameterName(String name) {
this.logoutParameterName = name;
}
public void setCasServerUrlPrefix(String casServerUrlPrefix) {
this.casServerUrlPrefix = casServerUrlPrefix;
}
public void setLogoutCallbackPath(String logoutCallbackPath) {
this.logoutCallbackPath = logoutCallbackPath;
}
public void setRelayStateParameterName(String name) {
this.relayStateParameterName = name;
}
public void setEagerlyCreateSessions(boolean eagerlyCreateSessions) {
this.eagerlyCreateSessions = eagerlyCreateSessions;
}
public void setClusterNodeUrls(String clusterNodeUrls) {
this.clusterNodeUrls = clusterNodeUrls;
}
public synchronized void init() {
if (this.safeParameters == null) {
CommonUtils.assertNotNull(this.artifactParameterName, "artifactParameterName cannot be null.");
CommonUtils.assertNotNull(this.logoutParameterName, "logoutParameterName cannot be null.");
CommonUtils.assertNotNull(this.sessionMappingStorage, "sessionMappingStorage cannot be null.");
CommonUtils.assertNotNull(this.relayStateParameterName, "relayStateParameterName cannot be null.");
CommonUtils.assertNotNull(this.casServerUrlPrefix, "casServerUrlPrefix cannot be null.");
if (CommonUtils.isBlank(this.casServerUrlPrefix)) {
this.logger.warn("Front Channel single sign out redirects are disabled when the 'casServerUrlPrefix' value is not set.");
}
if (this.artifactParameterOverPost) {
this.safeParameters = Arrays.asList(this.logoutParameterName, this.artifactParameterName);
} else {
this.safeParameters = Collections.singletonList(this.logoutParameterName);
}
}
}
private boolean isTokenRequest(HttpServletRequest request) {
return CommonUtils.isNotBlank(CommonUtils.safeGetParameter(request, this.artifactParameterName, this.safeParameters));
}
private boolean isLogoutRequest(HttpServletRequest request) {
if (!"POST".equalsIgnoreCase(request.getMethod())) {
return "GET".equalsIgnoreCase(request.getMethod()) ? CommonUtils.isNotBlank(CommonUtils.safeGetParameter(request, this.logoutParameterName, this.safeParameters)) : false;
} else {
return !this.isMultipartRequest(request) && this.pathEligibleForLogout(request) && CommonUtils.isNotBlank(CommonUtils.safeGetParameter(request, this.logoutParameterName, this.safeParameters));
}
}
private boolean pathEligibleForLogout(HttpServletRequest request) {
return this.logoutCallbackPath == null || this.logoutCallbackPath.equals(this.getPath(request));
}
private String getPath(HttpServletRequest request) {
return request.getServletPath() + CommonUtils.nullToEmpty(request.getPathInfo());
}
public boolean process(HttpServletRequest request, HttpServletResponse response) {
if (this.isTokenRequest(request)) {
this.logger.trace("Received a token request");
this.recordSession(request);
return true;
} else if (this.isLogoutRequest(request)) {
this.logger.trace("Received a logout request");
this.destroySession(request);
return false;
}else if(this.isLogoutRequestFromClusterNode(request)){//接收其它节点发送的http logout请求
//清除本节点session
this.logger.trace("Received a logout request from cluster node");
this.destroySessionFromClusterNode(request);
return false;
} else {
this.logger.trace("Ignoring URI for logout: {}", request.getRequestURI());
return true;
}
}
private void recordSession(HttpServletRequest request) {
HttpSession session = request.getSession(this.eagerlyCreateSessions);
if (session == null) {
this.logger.debug("No session currently exists (and none created). Cannot record session information for single sign out.");
} else {
String token = CommonUtils.safeGetParameter(request, this.artifactParameterName, this.safeParameters);
this.logger.debug("Recording session for token {}", token);
try {
this.sessionMappingStorage.removeBySessionById(session.getId());
} catch (Exception var5) {
;
}
this.sessionMappingStorage.addSessionById(token, session);
}
}
private String uncompressLogoutMessage(String originalMessage) {
byte[] binaryMessage = DatatypeConverter.parseBase64Binary(originalMessage);
Inflater decompresser = null;
String var6;
try {
decompresser = new Inflater();
decompresser.setInput(binaryMessage);
byte[] result = new byte[binaryMessage.length * 10];
int resultLength = decompresser.inflate(result);
var6 = new String(result, 0, resultLength, "UTF-8");
} catch (Exception var10) {
this.logger.error("Unable to decompress logout message", var10);
throw new RuntimeException(var10);
} finally {
if (decompresser != null) {
decompresser.end();
}
}
return var6;
}
private void destroySession(HttpServletRequest request) {
String logoutMessage = CommonUtils.safeGetParameter(request, this.logoutParameterName, this.safeParameters);
if (CommonUtils.isBlank(logoutMessage)) {
this.logger.error("Could not locate logout message of the request from {}", this.logoutParameterName);
} else {
if (!logoutMessage.contains("SessionIndex")) {
logoutMessage = this.uncompressLogoutMessage(logoutMessage);
}
this.logger.trace("Logout request:\n{}", logoutMessage);
String token = XmlUtils.getTextForElement(logoutMessage, "SessionIndex");
if (CommonUtils.isNotBlank(token)) {
HttpSession session = this.sessionMappingStorage.removeSessionByMappingId(token);
if (session != null) {
String sessionId = session.getId();
this.logger.debug("Invalidating session [{}] for token [{}]", sessionId, token);
try {
session.invalidate();
} catch (IllegalStateException var7) {
this.logger.debug("Error invalidating session.", var7);
}
this.logoutStrategy.logout(request);
}else {//session不在当前节点
logger.info("******destroySession session不在当前节点******");
//清除其他节点,采用广播形式发送http请求
destroySessionOfClusterNodes(token);
}
}
}
}
/**
* 判断是否是其它节点发送的logout通知
* @param request
* @return
*/
public boolean isLogoutRequestFromClusterNode(HttpServletRequest request) {
logger.info("isLogoutRequestFromClusterNode begin---");
logger.info("clusterNodeUrls=" + this.clusterNodeUrls);
logger.info("request.getParameter(this.logoutParameterClusterName)=" + request.getParameter(this.logoutParameterClusterName));
logger.info("isLogoutRequestFromClusterNode end---");
return (!isMultipartRequest(request)) && ("true".equals(request.getParameter(this.logoutParameterClusterName)));
}
/**
* 采用广播形式发送http请求,通知其他节点清除session
* @param token
*/
private void destroySessionOfClusterNodes(String token) {
//广播到所有节点
logger.info("******destroySessionOfClusterNodes begin******:" + token);
if(this.clusterNodeUrls != null && this.clusterNodeUrls.length() > 0){
logger.info("--clusterNodeUrls--"+clusterNodeUrls);
String[] clusters = this.clusterNodeUrls.split(",");
for (String url : clusters) {
logger.info("--url--"+url);
Map<String,String> mapParams = new HashMap<String, String>();
mapParams.put(this.logoutParameterClusterName,"true");
mapParams.put(this.artifactParameterName,token);
HttpUtil.httpPostWithParams(url,mapParams);
}
}
logger.info("******destroySessionOfClusterNodes end******:" + token);
}
/**
* 接收从其它节点的通知,清除session
* @param request
*/
public void destroySessionFromClusterNode(HttpServletRequest request){
String token = request.getParameter(this.artifactParameterName);
logger.info("******destroySessionFromClusterNode begin******:" + token);
if(CommonUtils.isNotBlank(token)){
final HttpSession session = sessionMappingStorage.removeSessionByMappingId(token);
if(session != null){
String sessionID = session.getId();
if(logger.isDebugEnabled()){
logger.debug("Invalidating session[" + sessionID +"] for token [" + token + "]");
}
try {
session.invalidate();
} catch (final IllegalStateException e) {
logger.debug("Error invalidating session",e);
}
}
}
logger.info("******destroySessionFromClusterNode end******:" + token);
}
private boolean isMultipartRequest(HttpServletRequest request) {
return request.getContentType() != null && request.getContentType().toLowerCase().startsWith("multipart");
}
private static boolean isServlet30() {
try {
return HttpServletRequest.class.getMethod("logout") != null;
} catch (NoSuchMethodException var1) {
return false;
}
}
private class Servlet30LogoutStrategy implements SingleSignOutHandler.LogoutStrategy {
private Servlet30LogoutStrategy() {
}
public void logout(HttpServletRequest request) {
try {
request.logout();
} catch (ServletException var3) {
SingleSignOutHandler.this.logger.debug("Error performing request.logout.");
}
}
}
private class Servlet25LogoutStrategy implements SingleSignOutHandler.LogoutStrategy {
private Servlet25LogoutStrategy() {
}
public void logout(HttpServletRequest request) {
}
}
private interface LogoutStrategy {
void logout(HttpServletRequest var1);
}
}
在配置文件里面配置好客户端的集群地址clusterNodeUrls ,多个用逗号分隔。
主要的代码段:
接收退出通知
判断是否其他节点的通知:
经测试后,可以使用