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

我的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. 在ASP.NET Core中编写合格的中间件

    这篇文章探讨了让不同的请求去使用不同的中间件,那么我们应该如何配置ASP.NET Core中间件?其实中间件只是在ASP.NET Core中处理Web请求的管道.所有ASP.NET Core应用程序至 ...

  2. MUI 混合开发移动app应用开发 --- app版本升级

    当我们的app开发完成之后,无可避免的以后会进行产品升级,那么我们希望在客户的手机上让app进行自动升级,可以分为自动升级和手动升级. 自动升级:一般在客户app第一次打开首页的时候. 手动升级:在a ...

  3. MUI错误信息:系统已经存在较高版本,些安装包无法安装。

    MUI 混合开发APP 版本更新问题. 错误信息: 解决方法: manifest.json->version->code 这个值需要累加,version->name 是用于显示的,这 ...

  4. redis入门(三)

    目录 redis入门(三) 目录 前言 事务 原理 Lua脚本 安装 脚本命令 集群搭建工具 redis-trib.rb redis官方集群搭建 集群横向扩展 故障转移 redis管理 参考文档 re ...

  5. 前端技术之:如何在Vue中使用clipboard.js复制服务端数据

    第一步 创建点击对象页面元素,并绑定业务数据. <el-button type="text" size="mini" class="copy-b ...

  6. MIT线性代数:12.图和网络

  7. WPF项目设计规则

    关于WPF应用程序的设计: 项目构成,自上而下: 1. 前端的WPF应用程序项目. 2. 业务逻辑的类库项目. 3. 映射到数据库各个表的Model型类库,(数据库操作接口定义). 4. 其他工具型类 ...

  8. [考试反思]1004csp-s模拟测试59:惊醒

    一句话:我看错考试时间了,我以为11:30结束,T2T3暴力没来得及交. 为什么考试的时间忽然变了啊...没转过来 一定要看清考试的起止时间! 虽说T2T3连爆搜都没打,只打特殊性质只有32分.爆搜分 ...

  9. [考试反思]0803NOIP模拟测试12:偿还

    嗯,rank5.没什么可评价的,高不算高低不算低. 一套好题,被我浪费了. 离上面280的大神差的有点远. 分机房的绝响就要来临. 越来越感觉自己变菜了,整体的能力水平在下滑. 说的不只是考试,包括平 ...

  10. python——函数的形参和实参、参数

    python的参数分类 python参数可以分为两类:1.定义时的参数--形参(形式参数).2.调用时的参数--实参(实际参数,传参) 实参的规则 实参就是在函数调用的时候,通过函数后面的括号传递给函 ...