SNI缺失导致JAVA程序HTTPS访问失败记录
背景
公司开发了一个Java程序与客户的4A认证服务进行对接,应用层协议为HTTP。
4A认证服务部署在PAAS平台内,由HAproxy的443端口代理,HAproxy做证书卸载与转发。
问题
正常情况下,当JAVA程序使用https协议向4A发起请求时,4A收到请求应该返回一个包含用户信息的JSON串;但测试时,该请求未能被正确处理,并返回了一个html页面,JAVA程序不能处理html格式返回体导致报错。
分析
通过抓包确认,当JAVA发起请求后,HAproxy代接收到了该请求,但4A未收到。所以响应报文的html页面并不是4A返回的,而是由HAproxy代反的一个页面。
由于HAproxy是4A部署的,对具体配置我们并不了解,并且客户也表示不能提供。加上本人此前对HAproxy的认识还停留在负载均衡与端口转发上,所以让客户去联系4A为何请求到了443,却没有正确转发。
等待两天后,4A确认请求到了HAproxy,但对于为何没有转发表示同样不清除,猜测是请求方式不对。
由于该JAVA程序在客户现有的生产和测试环境中,都已经部署并且是正常运行状态,本次测试对接与测试环境对接的还是同一套4A,JAVA程序的版本,服务配置也都是一样的,便暂时没有了分析方向。于是怀着死马当活马医的心态,让客户在HAproxy上再起一个监听端口,并且不配置证书证书卸载,结果可以了!!!
结论
通过上面的过程,基本可以确认JAVA程序及配置并没有问题,4A服务本身也没有问题。问题肯定是出在了https的这个“s”上面。抓包分析已有测试环境和当前环境TLS握手流量包,查看到确实有区别
4A对接正常的测试环境TLS握手client hello包
4A对接不正常的TLS握手client hello包
可以看到,两个TLS握手虽然都成功了,但是正常的client hello中带有server_name的Extension拓展字段,而不正常的client hello中没有该拓展字段,通过上网搜索关键字“HAproxy server_name”了解HAproxy SNI转发这回事...
SNI是从JDK7开始支持的,发起请求时没有server_name字段,是JDK版本太低的一个BUG,
bug详情见:https://bugs.openjdk.org/browse/JDK-8144566
该问题在JDK1.8.0_144版本后完成了修复,所以升级了JDK后问题解决了...
拓展
SNI概念(来自KIMI AI):
TLS SNI(Server Name Indication)是TLS协议的一个扩展,它允许客户端在TLS握手过程中指定要访问的域名。这个机制使得服务器能够在同一IP地址和端口上为多个域名提供服务,每个域名都有自己的SSL/TLS证书。在SNI出现之前,如果一个服务器托管多个网站并且每个网站都有自己的SSL证书,那么服务器就无法知道客户端想要连接到哪个网站,因为客户端的请求信息(包括域名)是在TLS握手之后的HTTP请求中才发送的。SNI通过在TLS握手的ClientHello消息中包含域名信息,解决了这个问题。
SNI的主要作用包括:
- 允许一个服务器使用多个域名和证书:通过在TLS握手阶段发送域名信息,服务器可以为每个域名提供正确的证书,从而支持虚拟主机技术。
- 提高IP地址的利用率:由于可以在同一IP上托管多个域名,减少了对独立IP地址的需求,有助于缓解IPv4地址短缺的问题。
- 增强安全性:确保客户端能够与正确的服务器域名建立安全连接,避免了证书错误和连接失败的问题。
SNI在TLSv1.2开始得到支持,并从OpenSSL 0.9.8版本开始实现,因此市场上的大多数终端设备都支持SNI。对于HTTPS网站来说,SNI是至关重要的,因为它允许服务器在同一IP地址上为多个域名提供加密连接,不需要为每个域名单独部署一台服务器,从而降低了运维成本并提高了灵活性。
HAproxy SNI配置
frontend https_proxy
mode tcp
bind :443
tcp-request inspect-delay 5s
tcp-request content accept if { req_ssl_hello_type 1 } #如果客户端发送的SSL hello包类型为1(即客户端hello),则接受该连接。
acl is_alibaba.com req_ssl_sni -i alibaba.com #定义一个访问控制列表(ACL),名为is_site1.example.com,如果客户端在SSL握手中指定的服务器名称指示(SNI)是site1.example.com,则匹配该ACL。
acl is_weixin.com req_ssl_sni -i weixin.com #定义另一个ACL,名为is_site2.example.com,如果SNI是site2.example.com,则匹配该ACL。
use_backend alibaba.com if is_alibaba.com #如果匹配了is_site1.example.com ACL,则使用名为site1.example.com的后端(backend)来处理流量。
use_backend weixin.com if is_weixin.com #如果匹配了is_site2.example.com ACL,则使用名为site2.example.com的后端来处理流量。
backend alibaba.com
server website 192.168.1.1:443
backend weixin.com
server website 192.168.1.2:443
本文来自博客园,作者:Linux小飞象,转载请注明原文链接:https://www.cnblogs.com/linux-xiaofeixiang/p/18558997