之前的项目一直是单节点,这次在生产系统中使用了负载均衡,一个应用部署了两个节点,负载均衡策略未知。这样在使用时发现了这么一个问题:在单点退出后,应用有时候可以退出,但有时还在登陆状态,这就很郁闷了。

我的cas版本是5.1.2。一点点排查,把每个节点的日志都打开,把日志级别设置成最小trace,功夫不负有心人,发现了问题:用户在浏览器访问的都是节点一,在cas单点退出后,cas服务端发送logoutRequest的post请求,结果负载均衡把请求发送到了节点二,节点二压根未访问过,logoutRequest请求当然不起作用。

问题找到了,看官可能觉得楼主有点low,首先,负载均衡具体什么策略,为什么会发错节点;第二,负载均衡多节点为什么不用session共享。下面我一一解答。

第一个,负载均衡不是我负责的,我只知道通过浏览器访问一个应用能保证每次都访问同个节点,这就保证了用户每次都携带登录信息。我以为采用的是nginx的ip的hash值,结果被告知不是,是通过硬件控制负载均衡的。

第二个,之前公司项目差不多都是单节点,几乎所有的应用都未使用session共享,况且现在是上线的关键节点,现改也来不及,最好是修改cas客户端的东西,大家都更新一下jar包就可以了。

        logger.trace("Logout request:\n{}", logoutMessage);
final String token = XmlUtils.getTextForElement(logoutMessage, "SessionIndex");
if (CommonUtils.isNotBlank(token)) {
final HttpSession session = this.sessionMappingStorage.removeSessionByMappingId(token); if (session != null) {
final String sessionID = session.getId();
logger.debug("Invalidating session [{}] for token [{}]", sessionID, token); try {
session.invalidate();
} catch (final IllegalStateException e) {
logger.debug("Error invalidating session.", e);
}
this.logoutStrategy.logout(request);
}

了解cas的同学知道,cas服务端发送logoutRequest的post请求,里面携带token信息,客户端就凭借这个token找到session。图中标红部分,当未找到时不起作用,解决之道就在此,我们拿到了token信息,然后向携带着token向应用的各个节点发送一次退出请求,这样就能找到session,然后失效退出了。

就这样,问题找到了,就着手解决吧。

第一步:修改cas客户端,当session为空时,发送post请求,上代码。

        logger.trace("Logout request:\n{}", logoutMessage);
final String token = XmlUtils.getTextForElement(logoutMessage, "SessionIndex");
if (CommonUtils.isNotBlank(token)) {
final HttpSession session = this.sessionMappingStorage.removeSessionByMappingId(token); if (session != null) {
final String sessionID = session.getId();
logger.debug("Invalidating session [{}] for token [{}]", sessionID, token); try {
session.invalidate();
} catch (final IllegalStateException e) {
logger.debug("Error invalidating session.", e);
}
this.logoutStrategy.logout(request);
}else{
if(CommonUtils.isNotBlank(this.balanceUrls)) {
String[] urls = balanceUrls.split(",");
for(String url : urls) {
Map<String, Object> map = new HashMap<String, Object>();
map.put("token",token);
try {
                //发送post请求,不要在意我的工具类名字,复制来的
HttpClient.GET_JCMH_QX(map, url + "/logoutRequestOther");
}catch(Exception e) {
logger.debug("Error HttpClient ", e);
}
}
}
}

比之前的代码多了else语句,这里的balanceUrls(在filter的initParam维护),是负载均衡的转发地址,多个用,隔开,然后遍历发送post请求,请求路径统一为“/logoutRequestOther“。

第二步在我们第一步的/logoutRequestOthe请求肯定是请求不到的,我们怎么处理呢?为了最小的改动,在cas客户端增加filter进行处理,增加LogoutForBalanceFilter类。

一定要跟SingleSignOutFilter同目录,原因是我们需要用到 private SessionMappingStorage sessionMappingStorage ,session信息就存储在这个类中。

package org.jasig.cas.client.session;

import org.jasig.cas.client.util.CommonUtils;
import org.jasig.cas.client.util.JsonUtil;
import org.jasig.cas.client.util.XmlUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.InputStream;
import java.util.Map; public class LogoutForBalanceFilter implements Filter { private final Logger logger = LoggerFactory.getLogger(LogoutForBalanceFilter.class); private SessionMappingStorage sessionMappingStorage; @Override
public void init(FilterConfig filterConfig) throws ServletException {
} @Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain chain) throws IOException, ServletException { if (servletRequest instanceof HttpServletRequest) {
HttpServletRequest req = (HttpServletRequest) servletRequest;
String uri = req.getRequestURI();
logger.debug("================== uri [{}]", uri);
if (uri.contains("/logoutRequestOther")) {
if (sessionMappingStorage == null) {
sessionMappingStorage = getSessionMappingStorage();
}
         //获取post请求的参数
Map<?,?> map = JsonUtil.jsonStrToMap(getParams(req));
if(map != null) {
String token = (String)map.get("token");
logger.debug("================== token [{}]", token);
if (CommonUtils.isNotBlank(token)) {
              //通过token查找session
final HttpSession session = this.sessionMappingStorage.removeSessionByMappingId(token);
if (session != null) {
final String sessionID = session.getId();
logger.debug("Invalidating session [{}] for token [{}]", sessionID, token); try {
session.invalidate();
} catch (final IllegalStateException e) {
logger.debug("Error invalidating session.", e);
}
//this.logoutStrategy.logout(request);
}
}
}
return;
}
chain.doFilter(servletRequest, servletResponse);
}else {
chain.doFilter(servletRequest, servletResponse);
}
} @Override
public void destroy() { }
protected static SessionMappingStorage getSessionMappingStorage() {
return SingleSignOutFilter.getSingleSignOutHandler().getSessionMappingStorage();
} protected String getParams(HttpServletRequest request){
String body = "";
StringBuilder stringBuilder = new StringBuilder();
BufferedReader bufferedReader = null;
InputStream inputStream = null;
try {
inputStream = request.getInputStream();
if (inputStream != null) {
bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
char[] charBuffer = new char[128];
int bytesRead = -1;
while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
stringBuilder.append(charBuffer, 0, bytesRead);
}
} else {
stringBuilder.append("");
}
} catch (IOException ex) {
ex.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
}
catch (IOException e) {
e.printStackTrace();
}
}
if (bufferedReader != null) {
try {
bufferedReader.close();
}
catch (IOException e) {
e.printStackTrace();
}
}
}
body = stringBuilder.toString();
return body;
} }

至此客户端更改完毕。

这样,同事们只需要更改下web.xml,增加一个filter,添加负载均衡转发的地址参数列表。

    <filter>

        <filter-name>Cas_Balance_Logout</filter-name>

        <filter-class>org.jasig.cas.client.session.LogoutForBalanceFilter</filter-class>

    </filter>

    <filter-mapping>

        <filter-name>Cas_Balance_Logout</filter-name>

        <url-pattern>/logoutRequestOther</url-pattern>

    </filter-mapping>

    <filter>

     <filter-name>CAS Single Sign Out Filter</filter-name>

        <filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class>

        <init-param>

            <param-name>casServerUrlPrefix</param-name>

            <param-value>http://zsptsso.jlsw.tax.cn/cas</param-value>

        </init-param>

        <init-param>

            <param-name>balanceUrls</param-name>

            <param-value>http://123.14.62.125:8002/zntx,http://123.14.62.126:8002/zntx</param-value>

        </init-param>    

    </filter>

    <filter-mapping>

        <filter-name>CAS Single Sign Out Filter</filter-name>

        <url-pattern>/*</url-pattern>

    </filter-mapping>

    <listener>

        <listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class>

    </listener>

图中红色部分就是同事们需要增加的,是不是很简单呢,完美!

cas的客户端应用是负载均衡,单点退出怎么办?的更多相关文章

  1. spring-cloud-starter-ribbon提供客户端的软件负载均衡算法

    Ribbon是什么? Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法,将Netflix的中间层服务连接在一起.Ribbon客户端组件提供一系列完善的配置项如连接超时 ...

  2. spring cloud 通过 ribbon 实现客户端请求的负载均衡(入门级)

    项目结构 环境: idea:2020.1 版 jdk:8 maven:3.6.2 1. 搭建项目 ( 1 )父工程:spring_cloud_demo_parent pom 文件 <?xml v ...

  3. SpringCloud全家桶学习之客户端负载均衡及自定义负载均衡算法----Ribbon(三)

    一.Ribbon是什么? Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端  负载均衡的工具(这里区别于nginx的负载均衡).简单来说,Ribbon是Netf ...

  4. ice调通过iceReplica用所有server instance的方法---客户端控制服务端的负载均衡

    I 使用此方法,可以增量的通知Ice服务配置的改变,刷新每个服务进程的数据 可以手动控制客户端调用的负载均衡,客户端程序决定将请求发往那个进程 上代码: import logging import I ...

  5. Spring Cloud Ribbon——客户端负载均衡

    一.负载均衡负载均衡(Load Balance): 建立在现有网络结构之上,它提供了一种廉价有效透明的方法扩展网络设备和服务器的带宽.增加吞吐量.加强网络数据处理能力.提高网络的灵活性和可用性.其意思 ...

  6. 【Dalston】【第二章】客户端负载均衡(Ribbon)

    对于大型应用系统负载均衡(LB:Load Balancing)是首要被解决一个问题.在微服务之前LB方案主要是集中式负载均衡方案,在服务消费者和服务提供者之间又一个独立的LB,LB通常是专门的硬件,如 ...

  7. 客户端负载均衡Ribbon之一:Spring Cloud Netflix负载均衡组件Ribbon介绍

    Netflix:['netfliːks] ribbon:英[ˈrɪbən]美[ˈrɪbən]n. 带; 绶带; (打印机的) 色带; 带状物;v. 把…撕成条带; 用缎带装饰; 形成带状;     L ...

  8. Spring Cloud入门教程(二):客户端负载均衡(Ribbon)

    对于大型应用系统负载均衡(LB:Load Balancing)是首要被解决一个问题.在微服务之前LB方案主要是集中式负载均衡方案,在服务消费者和服务提供者之间又一个独立的LB,LB通常是专门的硬件,如 ...

  9. RabbitMQ客户端负载均衡算法

    负载均衡(Load balance)是一种计算机网络技术,用于在多个计算机(计算机集群).网络连接.CPU.磁盘驱动器或其他资源中分配负载,以达到最佳资源使用.最大化吞吐率.最小响应时间以及避免过载的 ...

随机推荐

  1. web.xml不同版本的头信息

    web.xml v2.3 <?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE web- ...

  2. Java 基本数据类型的取值范围

    基本数据类型,字节数,位数,最大值和最小值. 1. 基本类型:short 二进制位数:16 包装类:java.lang.Short 最小值:Short.MIN_VALUE=-32768 (-2的15此 ...

  3. Spring Boot 快速入门 史上最简单

    1.Spring Boot 概述 Spring Boot 是所有基于 Spring 开发的项目的起点.Spring Boot 的设计是为了让你尽可能快的跑起来 Spring 应用程序并且尽可能减少你的 ...

  4. spring cloud 面试题总结

    前言,随着分布式的时代到来,现在微服务越来越火了,Spring Cloud已经成为一个面试必问的考点,下面我们就Cloud这一些列的组件来一个入门到面试的过程.开篇前,先让大家看几个常见的Spring ...

  5. (二)golang--windows下vscode的安装以及go环境的配置

    在官网上下载vscode对应的安装包,进行安装: 安装的时候记得勾选加入到path路径: 下载go语言的SDK:https://studygolang.com/dl目前最新版本是1.13.4,我就下这 ...

  6. 前端技术之:通过plop生成Controller的方法与步骤

    # Controller的生成 开发者可以通过plop命令生成各种类型的控制器类(Normal.Restful.View), 以下是示例生成步骤. 1. 执行以下命令: plop controller ...

  7. CSPS模拟 92

    为什么每次我的flag都会倒? skyh:12:15之前你把T2改出来我吃屎. ----12:10 于是12:12把线段树打完 12:13把主函数打完,过样例,带着一个sb错误交了,WA飞. 然后我就 ...

  8. C语言:互质

    今天遇到一道奇怪的程序题,和平常的不同.同样都是互质,但是一般的题目都是判断两个数字是否互质,但这道题则是给定一个数字n,要求输出所有小于等于n的与n互质的数,题目已经在下面给出: 质数与互质概念不是 ...

  9. Linux软件包管理和磁盘管理实践

    一.自建yum仓库,分别为网络源和本地源 本地yum仓库的搭建就是以下三个步骤: 创建仓库目录结构 上传相应的包到目录下,或者直接挂载光盘也行,如果挂载光盘,第三步就可以省略,因为光盘默认里有repo ...

  10. Comet OJ - 2019国庆欢乐赛 C题 两排房子

    ###题目链接### 题目大意:这里有横着的两排房子,给你每个房子的左端点和右端点.若两排房子中分别有两个房子 x y ,他们在横坐标上有重叠部分(端点重叠也算),则被称为 “对门” 关系. 问你总共 ...