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

我的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. MySQL数据库的安装与配置(windows)

    MySQL是目前最为流行的开放源码的数据库,是完全网络化的跨平台的关系型数据库系统,它是由瑞典MySQLAB公司开发,目前属于Oracle公司.任何人都能从Internet下载MySQL软件,而无需支 ...

  2. 省市区三级联动(vue)

    vue项目中使用到三级联动,现在自己实现一个三级联动,仅供大家参考一下,直接上代码. <template> <section class="container"& ...

  3. 练习Markdown基本语法

    这是一级标题 二级标题 三级标题 我就说一点(数字+英文句点.) 第二 在行首增加*或-,就会有下面的效果 嘿 嘿 嘿 这一部分是插入图片和引用 插入图片 用感叹号+[]+括号 直接复制粘贴~ 引用 ...

  4. 正则表达式和python中的re模块

    ---恢复内容开始--- 常用的正则匹配规则 元字符 量词 字符组 字符集 转义符 贪婪匹配 re模块使用正则表达式 实例引入(是否使用re模块和正则表达式的区别) # 不使用正则表达式 phone_ ...

  5. Pandas常用数据结构

    Pandas 概述 Pandas(Python Data Analysis Library)是基于NumPy 的一种工具,该工具是为了解决数据分析任务而创建的.Pandas 纳入了大量库和一些标准的数 ...

  6. [考试反思]1008csp-s模拟测试64:契机

    暴力没打满...有点垃圾... 考得稍绝望,啥也不会啊??? T3的测试点分治还写挂了... 其实就是没有沉下心好好的思考,在三道题上来回切换结果一个成型思路都没有 T2既然已经想到那一步了居然没有继 ...

  7. Apache配置反向代理、负载均衡和集群(mod_proxy方式)

    Apache配置负载均衡和集群使用mod_jk的方式比较多,但是mod_jk已经停止更新,并且配置相对复杂.Apache2.2以后,提供了一种原生的方式配置负载均衡和集群,比mod_jk简单很多. 1 ...

  8. scss新手使用指南

    还在用死的css写样式吗?那可太麻烦了,各种长串选择器不说,还有各种继承权重有时候还有可能不生效 我的小程序项目也结束了,是时候总结一下scss语法了,毕竟用起来更加方便而且还能精简一点代码,好处多多 ...

  9. [转载]1.2 UiPath第一个案例Hello World

    1.弹出框Hello World 在弹出的窗口中创建序列 在新建的序列中,在搜索框中输入“Message Box”,Studio自动搜索出结果. 选中“Message Box”,然后拖拽到界面带+号区 ...

  10. 计划任务at和crontab

    目标:会看,会写计划任务时间,会制定计划任务 一次性:at yum -y install at #安装at systemctl start atd #启动at服务 systemctl enable a ...