Apache FTPSClient登录正常但上传时出现SSL握手被远端关闭的报错求助
问题根本原因
你遇到的握手失败,核心是FTPS隐式连接下,数据通道的SSL握手没有复用控制通道的既有SSL会话,触发了服务器的安全拦截策略。
从堆栈可以看到,错误发生在FTPSClient._openDataConnection_阶段——也就是控制连接已经登录完成,客户端发起数据通道连接、准备做SSL握手的时候,服务器直接断开了TCP连接,根本没有回应SSL握手报文,因此抛出了EOFException: SSL peer shut down incorrectly。
使用WinSCP这类外部客户端可以正常连接,就是因为这类客户端默认开启了FTPS数据连接的SSL会话复用,符合服务器的安全校验要求;而你现有实现存在几个关键问题,导致会话复用没有真正生效:
现有代码的核心问题
- 自定义的SSL会话复用子类根本没有被实际使用
你在FTPS类里声明的ftpClient字段是原生FTPSClient类型,并没有实例化你编写的SSLSessionReuseFTPSClient子类,那你重写的_prepareDataSocket_方法自然不会被任何逻辑调用,完全起不到作用。 - 反射实现的会话复用逻辑本身存在兼容性问题
你编写的会话复用逻辑,是直接操作JDK内部SSLSessionContext的私有缓存,这个逻辑有两个硬伤:- 不同JDK版本对私有缓存
sessionHostPortCache的key生成规则做过多次修改,你硬编码的host:port小写key格式,在JDK8u291之后的版本中根本无法匹配到实际缓存项,塞进去的会话根本不会被数据通道的SSL握手读取到。 - 你在反射获取数据socket的host时,拿到的是被动模式下服务器返回的IP地址,和控制连接缓存里记录的主机名key根本对不上。
- 不同JDK版本对私有缓存
- 创建的SSLContext没有真正关联到FTPSClient
你在startClient方法里单独创建了一个TLSv1.2的SSLContext,只是从这个临时context创建了一个socket、读取了加密套件和协议列表传给FTPSClient,完全没有把这个SSLContext实例设置给FTPSClient自身的SSLSocket工厂。导致FTPSClient创建控制连接、数据连接时,用的还是JVM全局默认的SSLContext,你之前做的信任管理器配置、TLS协议强制、加密套件设置都没有真正作用到实际的socket上。 - JVM启动参数没有实际生效
你添加的-Dhttps.protocols=TLSv1.2参数,仅对JDK内置的HttpsURLConnection生效,完全不会影响原生SSLSocket、也不会影响FTPSClient的协议选择,属于无效配置。
对应解决方法
- 如果你使用的是Apache Commons Net 3.11.0及以上版本,直接在创建FTPSClient实例时开启内置的会话复用支持即可,不需要自行实现反射逻辑:
// 构造参数传true代表使用隐式FTPS,匹配990端口的连接场景 FTPSClient ftpsClient = new FTPSClient(true); // 开启内置的SSL会话复用校验,自动处理不同JDK版本的会话缓存兼容问题 ftpsClient.setSessionVerificationEnabled(true); // 信任证书配置 ftpsClient.setTrustManager(TrustManagerUtils.getAcceptAllTrustManager()); // 强制TLS1.2协议 ftpsClient.setEnabledProtocols(new String[]{"TLSv1.2"}); - 如果你使用的是3.11.0之前的旧版本,不要依赖自定义反射逻辑,通过统一的
SSLContext绑定所有连接配置:- 初始化自定义SSLContext时,把对应的信任管理器、支持的TLS协议、加密套件配置完整,不要再单独创建临时socket读取参数
- 通过
ftpsClient.setSSLContext(yourCustomSSLContext)把这个上下文实例传给FTPSClient,让它创建控制连接、数据连接时全部走这个上下文的SSLSocket工厂 - 在连接建立后、登录前按固定顺序执行安全配置:
ftpsClient.execPBSZ(0); ftpsClient.execPROT("P");
- 调整被动模式的调用时机,把
enterLocalPassiveMode()移到连接建立后、第一次数据操作之前,不要等到要调用storeFile时才设置。 - 去掉无效的JVM参数
-Dhttps.protocols=TLSv1.2,所有TLS相关的协议、加密套件配置,全部通过FTPSClient提供的API完成,不要依赖全局JVM参数。
内容的提问来源于stack exchange,提问作者dgtal
相关产品推荐
相关产品推荐

