运营模块访问 S3 报 TLS 证书主机名不匹配:一次依赖版本导致的「假证书问题」
摘要
某 Spring 应用在通过 **AWS SDK for Java v2** 上传对象到 **Amazon S3** 时,抛出 **TLS 主机名与证书不匹配**。环境(JDK、网络、凭证)与另一套可正常运行的部署一致,差异仅在 **Maven 依赖**。最终定位到 **`org.apache.httpcomponents:httpclient` 被显式锁定为旧版**,覆盖了 AWS `apache-client` 所需的版本;将 **HttpClient 升级到与 SDK 一致(如 4.5.12)** 后问题消失。
背景
-
应用使用 **`software.amazon.awssdk:s3`**(以及默认的 **`apache-client`**)访问对象存储。
-
部分业务代码封装在公共模块(下文称 **「公共模块」**)中;运营类 Web 应用(下文称 **「运营模块」**)单独引入依赖树。
-
现象:**同一套运行环境**下,A 部署正常,B 部署报错,对比发现 **B 未按与 A 相同方式引入公共模块或依赖版本不一致**。
现象与错误信息
上传或访问 S3 时出现类似异常(大意如下,具体主机名以实际为准):
-
**SSL / 证书校验失败**,提示 **peer 证书中的 CN/SAN** 与 **当前请求使用的主机名** 不一致。
-
典型表述中会同时出现:
-
访问域名形态类似:`*.s3.<region>.amazonaws.com`(点分区域名);
-
证书侧可能是另一套历史通配符形态(如连字符区域名),从而加重「像是证书配错了」的错觉。
> **说明**:S3 在不同 endpoint 风格下主机名可能不同;若仅看到「主机名与证书不一致」,容易误判为 **证书配置错误或 DNS 劫持**,而忽略 **本地 HTTP/TLS 栈版本过旧** 的可能。
排查思路(简要)
-
**确认 SDK 代际**:业务使用的是 **AWS SDK v2**(`software.amazon.awssdk`),不要与 v1(`com.amazonaws`)的依赖混谈。
-
**对比可运行与不可运行产物的依赖**:重点看 **`software.amazon.awssdk`** 各模块版本是否一致,以及 **`apache-client`** 传递的 **`httpclient`** 是否被覆盖。
-
**在 IDE 依赖分析或 `mvn dependency:tree` 中搜索** `org.apache.httpcomponents:httpclient`,确认 **最终解析版本**。
根因
在 **运营模块** 的 `pom.xml` 中,曾对 **`httpclient` 显式指定了较低版本**(例如 **4.4.x**)。
而 **`software.amazon.awssdk:apache-client`**(与 S3 等客户端配套)期望使用 **较新的 `httpclient`(例如 4.5.12)**。
Maven 解析时,**显式依赖往往优先**,导致:
-
传递依赖中的 **`httpclient:4.5.12` 被标记为「与 4.4.x 冲突而省略」**;
-
运行时实际加载 **旧版 HttpClient**,与 AWS 官方测试矩阵不一致;
-
在 **TLS、SNI、主机名校验** 等路径上可能出现与新版不同的行为,从而表现为 **证书/主机名相关异常**(与「endpoint 主机名形态」等问题叠加时,日志更容易误导排查方向)。
**结论**:表面是「证书与主机名不匹配」,实质之一是 **HTTP 客户端版本被业务 POM 钉死,未与 AWS SDK 的 `apache-client` 对齐**。
解决方案
推荐做法(择一或组合)
- **将 `httpclient` 升级到与 `apache-client` 传递依赖一致**
例如在问题模块中显式使用 **4.5.12**(或与当前 `software.amazon.awssdk` BOM 中 `apache-client` 要求一致的版本)。
- **去掉无必要的显式 `httpclient` 依赖**
若没有特殊理由必须锁定版本,可删除该显式依赖,交由 **AWS SDK BOM + `apache-client`** 管理传递版本。
- **在父 POM 的 `dependencyManagement` 中统一**
避免各子模块各自钉 `httpclient` 版本,防止再次出现「某模块把全应用拉回旧版」的情况。
与「引入公共模块」为何能缓解
若公共模块的 POM 已使用 **较新的 `httpclient`**,且运营模块 **改为依赖公共模块并移除冲突的显式旧版本**,依赖树会与可运行环境对齐,问题随之消失。
**根本仍是版本对齐**,而不是「魔法依赖」。
验证建议
- 构建后执行:
`mvn dependency:tree -Dincludes=org.apache.httpcomponents:httpclient`
确认最终解析为 **目标版本(如 4.5.12)**,且无与 AWS `apache-client` 冲突的省略提示。
- 在测试环境做一次 **S3 上传/下载冒烟**,确认无 TLS 异常。
经验总结
| 要点 | 说明 |
|------|------|
| **显式依赖会覆盖传递依赖** | 特别是 `httpclient` 这类基础库,钉版本前确认与云 SDK、Spring 等栈兼容。 |
| **证书类报错先看依赖树** | 与环境无关、仅构建差异时,优先比对 **HTTP/TLS 相关组件版本**。 |
| **SDK v1 / v2 与 HTTP 实现不要混为一谈** | v2 使用 `software.amazon.awssdk` + `apache-client` / `netty` 等,排查时对齐 BOM。 |
| **长期维护** | 用 **BOM** 或 **父 POM 统一管理** 云 SDK 与 Apache HttpClient,减少漂移。 |
结语
本次问题说明:**基础设施类依赖的版本漂移,会以「TLS/证书」这种高敏感方式暴露出来**。把 **`httpclient` 与 AWS SDK v2 的 `apache-client` 对齐** 后,问题得到解决;若团队内仍有多个模块手写旧版 `httpclient`,建议做一次全仓库扫描与统一治理。
*本文中的业务名、模块名、存储桶名、区域与版本号均为说明用途;请按自身环境替换为实际值,勿在公开渠道泄露账号、密钥与真实桶名。*