热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Tomcat容器下Zuul网关加解密后的第一次请求出现400错误的问题

问题现象某些前端发来的请求会在前端加密发送到网关,并在网关解密之后发到真正的微服务,并将结果加密返回给前端。实现网关加密后,发现一次加密请求后,紧接着的非加密GET请求,就会出现400的错误

问题现象

某些前端发来的请求会在前端加密发送到网关,并在网关解密之后发到真正的微服务,并将结果加密返回给前端。
实现网关加密后,发现一次加密请求后,紧接着的非加密GET请求,就会出现400的错误。再发一次相同的GET请求,就会正常,观察后端微服务的收到网关请求的accessLog,发现接收到的请求解析有问题:

## 400的请求
- - - [04/Jan/2018:19:48:30 +0800] "-" 400 - 0 0.000 - "-" null null 10.120.242.152
## 正常的请求
- - - [04/Jan/2018:19:50:18 +0800] "GET /v1/api/XXX HTTP/1.1" 200 156 11 0.011 http://www.xxx.com "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36" http-nio-8111-exec-28 10.120.242.151 10.120.242.152

问题定位

首先查看那次400请求的HTTP抓包,发现HTTP包结构是完整的:

19:48:30.224244 52:54:00:32:c5:5e > 52:54:00:66:bc:63, ethertype IPv4 (0x0800), length 1762: (tos 0x0, ttl 64, id 50111, offset 0, flags [DF], proto TCP (6), length 1748)
10.120.242.152.27725 > 10.120.242.151.8111: Flags [P.], cksum 0x00e7 (incorrect -> 0xfdf0), seq 917602625:917604321, ack 2125955651, win 29, options [nop,nop,TS val 793264903 ecr 3278809206], length 1696
0x0000: 4500 06d4 c3bf 4000 4006 7644 0a78 f298 E.....@.@.vD.x..
0x0010: 0a78 f297 6c4d 1faf 36b1 8141 7eb7 8243 .x..lM..6..A~..C
0x0020: 8018 001d 00e7 0000 0101 080a 2f48 4307 ............/HC.
0x0030: c36e a876 4745 5420 2f76 312f 6669 6e41 .n.vGET./v1/finA
0x0040: 6363 732f 6669 6e41 6363 2f75 7365 7242 ccs/finAcc/userB
0x0050: 616c 2f4b 4553 2048 5454 502f 312e 310d al/KES.HTTP/1.1.
。。。。。。
0x06d0: 0d0a 0d0a ....

在Tomcat容器代码处打断点,读取出来的内容是有残缺的:

image

前面那一段Get 和路径不见了

我们再看一下上一个加密请求的包内容:

11:03:27.703518 52:54:00:32:c5:5e > 52:54:00:66:bc:63, ethertype IPv4 (0x0800), length 1832: (tos 0x0, ttl 64, id 12872, offset 0, flags [DF], proto TCP (6), length 1818)
10.120.242.152.15124 > 10.120.242.151.8111: Flags [P.], cksum 0x012d (incorrect -> 0xd94b), seq 84397903:84399669, ack 2813208375, win 33, options [nop,nop,TS val 777069391 ecr 3262603428], length 1766
0x0000: 4500 071a 3248 4000 4006 0776 0a78 f298 E...2H@.@..v.x..
0x0010: 0a78 f297 3b14 1faf 0507 cf4f a7ae 2737 .x..;......O..'7
。。。。。。
0x06b0: 436f 6e74 656e 742d 4c65 6e67 7468 3a20 Content-Length:.
0x06c0: 3630 0d0a 436f 6e6e 6563 7469 6f6e 3a20 108.Connection:.
0x06d0: 4b65 6570 2d41 6c69 7665 0d0a 0d0a 7b22 Keep-Alive....{"
0x06e0: 7068 6f6e 654e 6f22 3a22 3235 3437 3635 phoneNo"
:"254765
0x06f0: 3433 3231 3030 222c 2270 6179 416d 6f75 432100"
,"payAmou
0x0700: 6e74 223a 3130 3030 3030 3030 2c22 7061 nt"
:10000000,"pa
0x0710: 7943 6849 6422 3a31 307d yChId"
:10}

发现末尾的Content-Length不对,应该是60,而不是108.
解密前的长度是108,而解密后的长度是60。可能是这个原因,导致了下一个请求Tomcat丢失处理了。

Debug修改Content-Length为60,问题不再出现。可见就是这个原因

我们在解密修改包的时候,并没有成功修改Content-length

解决方案

1.换容器,换成Jetty问题消失,JettyNIO不会处理Content-Length字段,但是换容器对整体改动大,而且我们的场景适合Tomcat(大量的短小请求)
2.每个请求新建HttpClient连接,对于不同连接,TomcatNIO不会丢失处理,但是这样有性能损耗,不推荐。
3.改对Content-length,这个肯定是最佳方案,但是找对修改的地方确实换了一些时间,这里贴出核心原理代码:

对于Zuul网关的每次请求,都是一次Ribbon调用,Ribbon调用有上下文,里面有ContentLength这一项:

RibbonRoutingFilter.java

protected RibbonCommandContext buildCommandContext(RequestContext context) {
HttpServletRequest request = context.getRequest();

MultiValueMap<String, String> headers = this.helper
.buildZuulRequestHeaders(request);
MultiValueMap<String, String> params = this.helper
.buildZuulRequestQueryParams(request);
String verb = getVerb(request);
InputStream requestEntity = getRequestBody(request);
if (request.getContentLength() <0 && !verb.equalsIgnoreCase("GET")) {
context.setChunkedRequestBody();
}

String serviceId = (String) context.get(SERVICE_ID_KEY);
Boolean retryable = (Boolean) context.get(RETRYABLE_KEY);

String uri = this.helper.buildZuulRequestURI(request);

// remove double slashes
uri = uri.replace("//", "/");

long cOntentLength= useServlet31 ? request.getContentLengthLong(): request.getContentLength();

return new RibbonCommandContext(serviceId, verb, uri, retryable, headers, params,
requestEntity, this.requestCustomizers, contentLength);
}

注意到long cOntentLength= useServlet31 ? request.getContentLengthLong(): request.getContentLength();这个方法,对于Tomcat,request就是org.apache.catalina.connector.Request这个类:

@Override
public long getContentLengthLong() {
return coyoteRequest.getContentLengthLong();
}

@Override
public int getContentLength() {
return coyoteRequest.getContentLength();
}

再进一步看coyoteRequest的相关方法:

public int getContentLength() {
long length = getContentLengthLong();

if (length return (int) length;
}
return -1;
}

public long getContentLengthLong() {
if( contentLength > -1 ) {
return contentLength;
}

MessageBytes clB = headers.getUniqueValue("content-length");
cOntentLength= (clB == null || clB.isNull()) ? -1 : clB.getLong();

return contentLength;
}

所以,我们在解密完包之后,对于Tomcat需要修改ContentLength,修改方式就是添加如下代码到你解密使用的Wrapper或者Filter中:

//Only for tomcat, fix content-length or there will be bugs
if (request instanceof com.netflix.zuul.http.HttpServletRequestWrapper) {
com.netflix.zuul.http.HttpServletRequestWrapper request1 = (com.netflix.zuul.http.HttpServletRequestWrapper) request;
RequestFacade requestFacade = (RequestFacade) request1.getRequest();
try {
Field field = RequestFacade.class.getDeclaredField("request");
field.setAccessible(true);
Request o = (Request) field.get(requestFacade);
//将Content-length放进去
o.getCoyoteRequest().setContentLength(this.contentLength);
} catch (Exception e) {
log.info("catch exception: ", e);
}
}


推荐阅读
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • 阿,里,云,物,联网,net,core,客户端,czgl,aliiotclient, ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • 标题: ... [详细]
  • 解决nginx启动报错epoll_wait() reported that client prematurely closed connection的方法
    本文介绍了解决nginx启动报错epoll_wait() reported that client prematurely closed connection的方法,包括检查location配置是否正确、pass_proxy是否需要加“/”等。同时,还介绍了修改nginx的error.log日志级别为debug,以便查看详细日志信息。 ... [详细]
  • Spring常用注解(绝对经典),全靠这份Java知识点PDF大全
    本文介绍了Spring常用注解和注入bean的注解,包括@Bean、@Autowired、@Inject等,同时提供了一个Java知识点PDF大全的资源链接。其中详细介绍了ColorFactoryBean的使用,以及@Autowired和@Inject的区别和用法。此外,还提到了@Required属性的配置和使用。 ... [详细]
  • 基于Socket的多个客户端之间的聊天功能实现方法
    本文介绍了基于Socket的多个客户端之间实现聊天功能的方法,包括服务器端的实现和客户端的实现。服务器端通过每个用户的输出流向特定用户发送消息,而客户端通过输入流接收消息。同时,还介绍了相关的实体类和Socket的基本概念。 ... [详细]
  • 本文讨论了在VMWARE5.1的虚拟服务器Windows Server 2008R2上安装oracle 10g客户端时出现的问题,并提供了解决方法。错误日志显示了异常访问违例,通过分析日志中的问题帧,找到了解决问题的线索。文章详细介绍了解决方法,帮助读者顺利安装oracle 10g客户端。 ... [详细]
  • 如何在HTML中获取鼠标的当前位置
    本文介绍了在HTML中获取鼠标当前位置的三种方法,分别是相对于屏幕的位置、相对于窗口的位置以及考虑了页面滚动因素的位置。通过这些方法可以准确获取鼠标的坐标信息。 ... [详细]
  • GreenDAO快速入门
    前言之前在自己做项目的时候,用到了GreenDAO数据库,其实对于数据库辅助工具库从OrmLite,到litePal再到GreenDAO,总是在不停的切换,但是没有真正去了解他们的 ... [详细]
  • Servlet多用户登录时HttpSession会话信息覆盖问题的解决方案
    本文讨论了在Servlet多用户登录时可能出现的HttpSession会话信息覆盖问题,并提供了解决方案。通过分析JSESSIONID的作用机制和编码方式,我们可以得出每个HttpSession对象都是通过客户端发送的唯一JSESSIONID来识别的,因此无需担心会话信息被覆盖的问题。需要注意的是,本文讨论的是多个客户端级别上的多用户登录,而非同一个浏览器级别上的多用户登录。 ... [详细]
  • 生成式对抗网络模型综述摘要生成式对抗网络模型(GAN)是基于深度学习的一种强大的生成模型,可以应用于计算机视觉、自然语言处理、半监督学习等重要领域。生成式对抗网络 ... [详细]
  • 关键词:Golang, Cookie, 跟踪位置, net/http/cookiejar, package main, golang.org/x/net/publicsuffix, io/ioutil, log, net/http, net/http/cookiejar ... [详细]
author-avatar
手机用户2502894277
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有