CAS客户端在集群环境下,退出失败的问题

最近在线上出现了,退出一个客户端后,另外一个客户端会出现退出失败的问题,一开始觉得这个问题是服务端的问题,于是查询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 ,多个用逗号分隔。

主要的代码段:

接收退出通知

判断是否其他节点的通知:

 

 

 经测试后,可以使用

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值