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

我的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. 【阿里云IoT+YF3300】8.物联网设备用户脚本开发

    除了我们必须熟悉的网页脚本,比如JavaScript.其实在工业自动化中,组态软件是必备脚本的,只是有的脚本语言风格类似C或类似Basic而已.比如昆仑通泰的组态屏中的组态软件.通过安装组态软件可以简 ...

  2. [springboot 开发单体web shop] 3. 用户注册实现

    目录 用户注册 ## 创建数据库 ## 生成UserMapper ## 编写业务逻辑 ## 编写user service UserServiceImpl#findUserByUserName 说明 U ...

  3. 前端技术之:使用npx创建一个Nuxt.js项目

    $ npx create-nuxt-app my-first-nuxtjs npx: 401 安装成功,用时 43.891 秒 > Generating Nuxt.js project in / ...

  4. 《Effective Java》 读书笔记(六)避免创建不必要的对象

    java 有很多修饰类的属性的关键字:常用的static,final 说说final和static吧,平时在编程的时候,这两个关键字很多时候都觉得可有可无,最多的时候就是他们俩同时出现----定义常量 ...

  5. 使用jieba分析小说人物出现次数

    分析: 1. 读取小说,以读的形式打开 with open('文件名.txt','r',encoding='utf8') as f: str = f.read() 2. 切割小说 ret = jieb ...

  6. 「考试」weight

    正解是树剖. 首先Kru求最小生成树. 然后分别考虑树边和非树边的答案. 首先是非树边,非树边链接的两个点在MST上能够构成一条链. 这条链上最大的那条边-1就是这条边的答案. 为什么. 模拟Kru的 ...

  7. Google I/O 大会上提出的UI优化相关

    1.ListView的Adapter Adapter在ListView中的工作原理是: 上图也正好反映出ListView使用了Adapter来适配数据源. 每一个Item条目都是通过Adapter.g ...

  8. 爬虫学习--Urllib库基本使用 Day1

    一.Urllib库详解 1.什么是Urllib Python内置的HTTP请求库 urllib.request    请求模块(模拟实现传入网址访问) urllib.error             ...

  9. Wycieczki 线性代数

    B. Wycieczki 题目描述 给定一张n个点m条边的带权有向图,每条边的边权只可能是1,2,3中的一种.将所有可能的路径按路径长度排序,请输出第k小的路径的长度,注意路径不一定是简单路径,即可以 ...

  10. PTA刷题记录(3)

    团队天梯赛-------(3)分值:15 给定一个 k 位整数 N=d​k−1​​10​k−1​​+⋯+d​1​​10​1​​+d​0​​ (0≤d​i​​≤9, i=0,⋯,k−1, d​k−1​​ ...