目录

异常现象:Caused by: java.io.EOFException

在服务器部署的微博爬虫系统中,偶尔会抛出以下异常:

Caused by: java.io.EOFException: SSL peer shut down incorrectly

通过搜索引擎查询,在 Stackoverflow 上找到了相关的讨论。该回答指出,明确指定 HTTPS 的协议版本即可解决此问题。例如:

System.setProperty("https.protocols", "TLSv1.1");

为了彻底理解其原理,以下将对 HTTPS 协议及其在 Java 中的实现进行详细梳理。

HTTPS 协议概述

根据维基百科关于 HTTPS 的介绍:

严格地讲,HTTPS 并不是一个单独的协议,而是对工作在一加密连接(TLS 或 SSL)上的常规 HTTP 协议的称呼。

TLS 与 SSL

  • SSL (Secure Sockets Layer) 是 TLS (Transport Layer Security) 的前身。
  • SSL 最初由 Netscape 公司推出,用于为 HTTPS 协议提供加密支持。
  • 随后,IETF (Internet Engineering Task Force) 将 SSL 进行标准化,并公布了第一版 TLS 标准文件。

协议发展历史

SSL

  • SSL 1.0:从未公开过。
  • SSL 2.0:1995 年 2 月发布。
  • SSL 3.0:1996 年发布。2014 年 10 月,Google 发现 SSL 3.0 存在设计缺陷(POODLE 攻击),建议禁用此一协议。

TLS

IETF 将 SSL 标准化后称为 TLSTLS 1.0SSL 3.0 的差异非常小。

  • TLS 1.0
  • TLS 1.1:2006 年发布
  • TLS 1.2:2008 年发布
  • TLS 1.3:2016 年发布

JDK 对 HTTPS 协议版本的支持

不同版本的 JDK 默认支持及启用的 HTTPS 协议版本有所不同,具体如下:

JDK 6

  • SSL v3
  • TLS v1 (默认)
  • TLS v1.1 (JDK 6 Update 111 及以上版本支持)

JDK 7

  • SSLv3
  • TLS v1 (默认)
  • TLS v1.1
  • TLS v1.2

JDK 8

  • SSL v3
  • TLS v1
  • TLS v1.1
  • TLS v1.2 (默认)

JSSE (Java Secure Socket Extension)

JSSE 实现了 SSL 和 TLS 协议,为 Java 应用提供安全通信能力。

JSSE 参数调节

可以通过系统属性(System Properties)对 JSSE 行为进行调节:

  • javax.net.debug:打印连接的详细信息。

    • 例如:-Djavax.net.debug=all 或者 -Djavax.net.debug=ssl:verbose
  • https.protocols:控制 Java 客户端通过 HttpsURLConnectionURL.openStream() 操作时使用的协议版本。

    • 例如:-Dhttps.protocols=TLSv1,TLSv1.1,TLSv1.2
    • 注意:对于非 HTTP 协议,可以通过 SocketFactorySSLContext 来控制。
  • jdk.tls.client.protocols:控制底层平台的 TLS 实现。

    • 例如:-Djdk.tls.client.protocols=TLSv1.1,TLSv1.2
  • http.agent:当初始化连接时,Java 会使用这个字符串作为 User-Agent

    • 例如:-Dhttp.agent="known agent"
  • java.net.useSystemProxies:使用系统本身的代理设置。

    • 例如:-Djava.net.useSystemProxies=true
  • http.proxyHosthttp.proxyPort:使用 HTTP 协议时的代理设置。

    • 例如:-Dhttp.proxyHost=proxy.example.com -Dhttp.proxyPort=8080
  • https.proxyHosthttps.proxyPort:与上述类似,区别在于针对 HTTPS 协议。
  • http.proxyUser, http.proxyPassword, https.proxyUser, https.proxyPassword:代理认证的用户名和密码。

查看服务器支持的协议版本

可以使用 nmap 工具扫描服务器支持的 HTTPS 协议版本及加密套件。

nmap --script ssl-enum-ciphers -p 443 api.weibo.com

返回的结果示例如下:

Starting Nmap 7.40 ( https://nmap.org ) at 2017-03-02 14:18 CST
Nmap scan report for api.weibo.com (180.149.135.176)
Host is up (0.039s latency).
Other addresses for api.weibo.com (not scanned): 180.149.135.230
PORT    STATE SERVICE
443/tcp open  https
| ssl-enum-ciphers:
|   SSLv3:
|     ciphers:
|       TLS_RSA_WITH_AES_256_CBC_SHA (rsa 2048) - A
|       TLS_RSA_WITH_CAMELLIA_256_CBC_SHA (rsa 2048) - A
|       TLS_RSA_WITH_AES_128_CBC_SHA (rsa 2048) - A
|       TLS_RSA_WITH_SEED_CBC_SHA (rsa 2048) - A
|       TLS_RSA_WITH_CAMELLIA_128_CBC_SHA (rsa 2048) - A
|       TLS_RSA_WITH_IDEA_CBC_SHA (rsa 2048) - A
|       TLS_RSA_WITH_RC4_128_SHA (rsa 2048) - C
|       TLS_RSA_WITH_3DES_EDE_CBC_SHA (rsa 2048) - C
|       TLS_RSA_WITH_DES_CBC_SHA (rsa 2048) - C
|       TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA (secp256r1) - A
|       TLS_DHE_RSA_WITH_AES_256_CBC_SHA (dh 1024) - A
|       TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA (dh 1024) - A
|       TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA (secp256r1) - A
|       TLS_DHE_RSA_WITH_AES_128_CBC_SHA (dh 1024) - A
|       TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA (dh 1024) - A
|       TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA (secp256r1) - C
|       TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA (dh 1024) - D
|     compressors:
|       NULL
|     cipher preference: server
|     warnings:
|       64-bit block cipher 3DES vulnerable to SWEET32 attack
|       64-bit block cipher DES vulnerable to SWEET32 attack
|       64-bit block cipher IDEA vulnerable to SWEET32 attack
|       Broken cipher RC4 is deprecated by RFC 7465
|       CBC-mode cipher in SSLv3 (CVE-2014-3566)
|       Key exchange (dh 1024) of lower strength than certificate key
|   TLSv1.0:
|     ciphers:
|       TLS_RSA_WITH_AES_256_CBC_SHA (rsa 2048) - A
|       TLS_RSA_WITH_CAMELLIA_256_CBC_SHA (rsa 2048) - A
|       TLS_RSA_WITH_AES_128_CBC_SHA (rsa 2048) - A
|       TLS_RSA_WITH_SEED_CBC_SHA (rsa 2048) - A
|       TLS_RSA_WITH_CAMELLIA_128_CBC_SHA (rsa 2048) - A
|       TLS_RSA_WITH_IDEA_CBC_SHA (rsa 2048) - A
|       TLS_RSA_WITH_RC4_128_SHA (rsa 2048) - C
|       TLS_RSA_WITH_3DES_EDE_CBC_SHA (rsa 2048) - C
|       TLS_RSA_WITH_DES_CBC_SHA (rsa 2048) - C
|       TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA (secp256r1) - A
|       TLS_DHE_RSA_WITH_AES_256_CBC_SHA (dh 1024) - A
|       TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA (dh 1024) - A
|       TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA (secp256r1) - A
|       TLS_DHE_RSA_WITH_AES_128_CBC_SHA (dh 1024) - A
|       TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA (dh 1024) - A
|       TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA (secp256r1) - C
|       TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA (dh 1024) - D
|     compressors:
|       NULL
|     cipher preference: server
|     warnings:
|       64-bit block cipher 3DES vulnerable to SWEET32 attack
|       64-bit block cipher DES vulnerable to SWEET32 attack
|       64-bit block cipher IDEA vulnerable to SWEET32 attack
|       Broken cipher RC4 is deprecated by RFC 7465
|       Key exchange (dh 1024) of lower strength than certificate key
|   TLSv1.1:
|     ciphers:
|       TLS_RSA_WITH_AES_256_CBC_SHA (rsa 2048) - A
|       TLS_RSA_WITH_CAMELLIA_256_CBC_SHA (rsa 2048) - A
|       TLS_RSA_WITH_AES_128_CBC_SHA (rsa 2048) - A
|       TLS_RSA_WITH_SEED_CBC_SHA (rsa 2048) - A
|       TLS_RSA_WITH_CAMELLIA_128_CBC_SHA (rsa 2048) - A
|       TLS_RSA_WITH_IDEA_CBC_SHA (rsa 2048) - A
|       TLS_RSA_WITH_RC4_128_SHA (rsa 2048) - C
|       TLS_RSA_WITH_3DES_EDE_CBC_SHA (rsa 2048) - C
|       TLS_RSA_WITH_DES_CBC_SHA (rsa 2048) - C
|       TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA (secp256r1) - A
|       TLS_DHE_RSA_WITH_AES_256_CBC_SHA (dh 1024) - A
|       TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA (dh 1024) - A
|       TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA (secp256r1) - A
|       TLS_DHE_RSA_WITH_AES_128_CBC_SHA (dh 1024) - A
|       TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA (dh 1024) - A
|       TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA (secp256r1) - C
|       TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA (dh 1024) - D
|     compressors:
|       NULL
|     cipher preference: server
|     warnings:
|       64-bit block cipher 3DES vulnerable to SWEET32 attack
|       64-bit block cipher DES vulnerable to SWEET32 attack
|       64-bit block cipher IDEA vulnerable to SWEET32 attack
|       Broken cipher RC4 is deprecated by RFC 7465
|       Key exchange (dh 1024) of lower strength than certificate key
|   TLSv1.2:
|     ciphers:
|       TLS_RSA_WITH_AES_256_GCM_SHA384 (rsa 2048) - A
|       TLS_RSA_WITH_AES_256_CBC_SHA256 (rsa 2048) - A
|       TLS_RSA_WITH_AES_256_CBC_SHA (rsa 2048) - A
|       TLS_RSA_WITH_CAMELLIA_256_CBC_SHA (rsa 2048) - A
|       TLS_RSA_WITH_AES_128_GCM_SHA256 (rsa 2048) - A
|       TLS_RSA_WITH_AES_128_CBC_SHA256 (rsa 2048) - A
|       TLS_RSA_WITH_AES_128_CBC_SHA (rsa 2048) - A
|       TLS_RSA_WITH_SEED_CBC_SHA (rsa 2048) - A
|       TLS_RSA_WITH_CAMELLIA_128_CBC_SHA (rsa 2048) - A
|       TLS_RSA_WITH_IDEA_CBC_SHA (rsa 2048) - A
|       TLS_RSA_WITH_RC4_128_SHA (rsa 2048) - C
|       TLS_RSA_WITH_3DES_EDE_CBC_SHA (rsa 2048) - C
|       TLS_RSA_WITH_DES_CBC_SHA (rsa 2048) - C
|       TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (secp256r1) - A
|       TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 (secp256r1) - A
|       TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA (secp256r1) - A
|       TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 (dh 1024) - A
|       TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 (dh 1024) - A
|       TLS_DHE_RSA_WITH_AES_256_CBC_SHA (dh 1024) - A
|       TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA (dh 1024) - A
|       TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (secp256r1) - A
|       TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 (secp256r1) - A
|       TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA (secp256r1) - A
|       TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 (dh 1024) - A
|       TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 (dh 1024) - A
|       TLS_DHE_RSA_WITH_AES_128_CBC_SHA (dh 1024) - A
|       TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA (dh 1024) - A
|       TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA (secp256r1) - C
|       TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA (dh 1024) - D
|     compressors:
|       NULL
|     cipher preference: server
|     warnings:
|       64-bit block cipher 3DES vulnerable to SWEET32 attack
|       64-bit block cipher DES vulnerable to SWEET32 attack
|       64-bit block cipher IDEA vulnerable to SWEET32 attack
|       Broken cipher RC4 is deprecated by RFC 7465
|       Key exchange (dh 1024) of lower strength than certificate key
|_  least strength: D
Nmap done: 1 IP address (1 host up) scanned in 10.99 seconds

根据扫描结果可知,该服务器支持的协议包括:

  • SSLv3
  • TLSv1.0
  • TLSv1.1
  • TLSv1.2

解决方案

针对上述异常,在服务器环境(Linux + JDK7 + 64 位)中,通过在代码初始化阶段添加以下设置:

System.setProperty("https.protocols", "TLSv1.2");

强制指定使用 TLSv1.2 协议后,爬虫系统在爬取数据时不再抛出此类异常。

参考资料

说明:本文部分内容基于较早的 JDK 版本(如 JDK 6/7/8 早期版本)整理。随着安全标准的提升,现代 JDK 版本(如 JDK 8u291+、JDK 11+ 等)已默认禁用 TLS 1.0 和 TLS 1.1 协议。在实际生产环境中,建议优先使用 TLS 1.2 或 TLS 1.3,并及时更新 JDK 补丁以确保安全性。