【无标题】

SpringAiAlibaba之mitmproxy证书安装与原理解析(九)

前言

在调试 Spring AI 调用 DashScope API 的过程中,我遇到了一个典型的 HTTPS 抓包问题。本文将详细记录如何解决证书验证错误,并深入解析 mitmproxy 如何实现 HTTPS 流量拦截的原理。

问题背景

初始错误

在尝试使用 mitmproxy 抓取 Java 应用的 HTTPS 请求时,遇到了以下错误:

复制代码
Caused by: sun.security.provider.certpath.SunCertPathBuilderException:
unable to find valid certification path to requested target
    at java.base/sun.security.provider.certpath.SunCertPathBuilder.build(SunCertPathBuilder.java:148)
    at java.base/sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:129)
    at java.base/java.security.cert.CertPathBuilder.build(CertPathBuilder.java:297)
    at java.base/sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:434)

测试场景

  • 测试类 : EnableThinkingExtraBodyTest
  • 测试方法 : testEnableThinkingWithExtraBody_UserScenario()
  • 目标 API : https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions
  • 框架: Spring AI 1.1.2
  • Java 版本: OpenJDK 17 (Homebrew)
  • 抓包工具: mitmproxy/mitmweb

配置情况

已经在 IDEA 的 VM options 中配置了代理:

复制代码
-Dhttp.proxyHost=127.0.0.1
-Dhttp.proxyPort=8080
-Dhttps.proxyHost=127.0.0.1
-Dhttps.proxyPort=8080

但仍然报证书错误。

问题根源分析

为什么会出现证书错误?

1. HTTPS 的证书验证机制

正常的 HTTPS 请求流程:

复制代码
┌─────────────┐                    ┌──────────────┐
│  Java App   │ ────HTTPS───────>  │ DashScope API│
│             │ <───SSL/TLS────    │   Server     │
└─────────────┘                    └──────────────┘
     │
     └── 验证服务器证书
         ├── 证书由受信任的 CA 签发?✓
         ├── 证书域名匹配?✓
         └── 证书未过期?✓

服务器的证书由**受信任的证书颁发机构(CA)**签发,Java 的 cacerts 信任库中包含这些 CA 的根证书,因此可以验证通过。

2. 加入代理后的请求流程

使用 mitmproxy 作为代理后:

复制代码
┌─────────────┐         ┌──────────────┐         ┌──────────────┐
│  Java App   │ ─HTTPS─>│  mitmproxy   │ ─HTTPS─>│ DashScope API│
│             │ <─SSL─  │   (Proxy)    │ <─SSL─  │   Server     │
└─────────────┘         └──────────────┘         └──────────────┘
     │                        │
     │                        └── mitmproxy 的自签名证书
     │
     └── ❌ 验证失败!
         └── mitmproxy 的证书不在信任库中

关键点

  • mitmproxy 充当中间人(Man-in-the-Middle)
  • Java 应用连接的是 mitmproxy,而不是真实的服务器
  • mitmproxy 出示的是自己签发的证书,不是 DashScope 的证书
  • Java 的 cacerts 信任库中没有 mitmproxy 的 CA 证书
  • 因此证书验证失败
3. 为什么代码中禁用 SSL 验证无效?

尝试在代码中通过 HttpsURLConnection.setDefaultSSLSocketFactory() 禁用 SSL 验证:

java 复制代码
private static void disableSSLVerification() {
    TrustManager[] trustAllCerts = new TrustManager[]{
        new X509TrustManager() {
            public void checkServerTrusted(X509Certificate[] certs, String authType) {}
        }
    };
    SSLContext sc = SSLContext.getInstance("SSL");
    sc.init(null, trustAllCerts, new java.security.SecureRandom());
    HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
}

为什么无效?

Spring AI 1.1.2 使用的是 Spring RestClient (Spring 6.1+ 的新 HTTP 客户端),它在创建时会独立初始化自己的 SSL 配置 ,不使用全局的 HttpsURLConnection 设置:

java 复制代码
// OpenAiApi 内部(简化)
this.restClient = RestClient.builder()
    .baseUrl(baseUrl)
    .requestFactory(createRequestFactory())  // 创建独立的请求工厂
    .build();

每个 RequestFactory 有自己的 SSL 上下文,不受全局设置影响。

解决方案:安装 mitmproxy 证书

操作步骤

1. 找到 mitmproxy 证书

mitmproxy 首次运行时会生成自签名的 CA 证书:

bash 复制代码
ls ~/.mitmproxy/
# 输出:
# mitmproxy-ca-cert.pem  <- 这是我们需要的
# mitmproxy-ca-cert.cer
# mitmproxy-ca.pem
2. 定位 Java 的 cacerts 信任库

对于 Homebrew 安装的 OpenJDK 17:

bash 复制代码
# 查找 OpenJDK 17 路径
ls -la /opt/homebrew/opt/openjdk@17

# cacerts 位置
/opt/homebrew/opt/openjdk@17/libexec/openjdk.jdk/Contents/Home/lib/security/cacerts
3. 导入证书到 Java 信任库
bash 复制代码
sudo /opt/homebrew/opt/openjdk@17/bin/keytool -importcert \
    -file ~/.mitmproxy/mitmproxy-ca-cert.pem \
    -alias mitmproxy \
    -keystore /opt/homebrew/opt/openjdk@17/libexec/openjdk.jdk/Contents/Home/lib/security/cacerts \
    -storepass changeit \
    -noprompt

参数说明

  • -importcert: 导入证书命令
  • -file: mitmproxy 的 CA 证书路径
  • -alias mitmproxy: 证书别名(便于后续管理)
  • -keystore: Java 信任库路径
  • -storepass changeit: 默认密码(Java cacerts 的默认密码)
  • -noprompt: 不提示确认
4. 验证证书是否安装成功
bash 复制代码
/opt/homebrew/opt/openjdk@17/bin/keytool -list \
    -keystore /opt/homebrew/opt/openjdk@17/libexec/openjdk.jdk/Contents/Home/lib/security/cacerts \
    -storepass changeit | grep mitmproxy

输出

复制代码
mitmproxy, 2026年1月14日, trustedCertEntry,

为什么安装证书后问题解决了?

安装证书后的验证流程:

复制代码
┌─────────────┐         ┌──────────────┐         ┌──────────────┐
│  Java App   │ ─HTTPS─>│  mitmproxy   │ ─HTTPS─>│ DashScope API│
│             │ <─SSL─  │   (Proxy)    │ <─SSL─  │   Server     │
└─────────────┘         └──────────────┘         └──────────────┘
     │                        │
     └── ✅ 验证通过!
         ├── mitmproxy 证书由 mitmproxy CA 签发
         ├── mitmproxy CA 根证书在 cacerts 中
         └── 信任链完整

信任链建立

复制代码
DashScope Server 的真实证书
         ↓
    mitmproxy 验证
         ↓
mitmproxy 重新签发证书(使用自己的 CA)
         ↓
    Java App 收到
         ↓
Java 验证证书 → 发现是 mitmproxy CA 签发
         ↓
在 cacerts 中找到 mitmproxy CA 根证书
         ↓
    验证通过 ✅

mitmproxy 工作原理深度解析

HTTPS 中间人攻击的合法应用

mitmproxy 实际上是在执行可控的中间人攻击,但用于合法的调试目的。

完整的请求路径分析

阶段 1: 建立到 mitmproxy 的连接
复制代码
1. Java App 发起 HTTPS 请求
   ↓
2. 通过代理配置连接到 127.0.0.1:8080
   ↓
3. 发送 CONNECT 请求:
   CONNECT dashscope.aliyuncs.com:443 HTTP/1.1
   Host: dashscope.aliyuncs.com:443
   ↓
4. mitmproxy 响应:
   HTTP/1.1 200 Connection established
阶段 2: TLS 握手(Java App ↔ mitmproxy)
复制代码
5. Java App 发起 TLS ClientHello
   ↓
6. mitmproxy 响应 ServerHello + 证书
   证书内容:
   - 主题: CN=dashscope.aliyuncs.com
   - 签发者: CN=mitmproxy  ← 注意:是 mitmproxy 签发的!
   - 有效期: mitmproxy 动态生成
   ↓
7. Java App 验证证书
   ├── 检查签发者: mitmproxy CA
   ├── 在 cacerts 中查找 mitmproxy CA 根证书 ✓
   ├── 验证证书链 ✓
   └── 验证域名匹配 ✓
   ↓
8. TLS 握手完成
   建立加密连接(使用对称密钥)
阶段 3: mitmproxy 转发到真实服务器
复制代码
9. mitmproxy 解密收到的请求
   (因为 mitmproxy 拥有私钥)
   ↓
10. mitmproxy 发起到真实服务器的 TLS 连接
    ┌────────────────────────────────┐
    │ mitmproxy ─TLS─> DashScope API │
    └────────────────────────────────┘
    - 验证 DashScope 的真实证书
    - 建立独立的加密连接
    ↓
11. mitmproxy 转发原始请求:
    POST /compatible-mode/v1/chat/completions HTTP/1.1
    Authorization: Bearer sk-xxx
    Content-Type: application/json

    {
      "model": "qwen-plus",
      "messages": [...],
      "enable_thinking": true
    }
阶段 4: 响应返回
复制代码
12. DashScope 返回响应
    ↓
13. mitmproxy 收到响应(明文,因为已解密)
    ↓
14. mitmproxy 记录/展示请求和响应(mitmweb 界面)
    ↓
15. mitmproxy 加密响应(使用与 Java App 的密钥)
    ↓
16. 发送给 Java App
    ↓
17. Java App 解密响应(使用自己的密钥)
    ↓
18. 应用程序收到响应

关键技术点

1. 动态证书生成

mitmproxy 为每个 HTTPS 域名动态生成证书

python 复制代码
# mitmproxy 内部伪代码
def generate_cert(hostname):
    cert = Certificate()
    cert.subject = f"CN={hostname}"  # 匹配目标域名
    cert.issuer = "CN=mitmproxy"     # 由 mitmproxy CA 签发
    cert.sign_with(mitmproxy_ca_key) # 使用 CA 私钥签名
    return cert

这样可以确保:

  • 证书的域名与目标服务器匹配(通过 SNI 检查)
  • 证书由 mitmproxy CA 签发(可以验证)
  • 每个域名都有独立的证书
2. 双向加密连接

mitmproxy 同时维护两个独立的 TLS 连接

复制代码
Java App ←─── TLS 连接 1 ───→ mitmproxy ←─── TLS 连接 2 ───→ DashScope
         (使用 mitmproxy 证书)            (使用 DashScope 真实证书)
  • 连接 1: 使用 mitmproxy 的自签名证书,Java App 需要信任 mitmproxy CA
  • 连接 2: 使用 DashScope 的真实证书,mitmproxy 验证真实证书
3. 流量解密与记录

因为 mitmproxy 拥有连接 1 的私钥,可以解密所有流量:

复制代码
加密的请求 → mitmproxy 解密 → 明文请求(可查看、修改)
                  ↓
              记录到日志
                  ↓
明文响应 ← mitmproxy 解密 ← 加密的响应

mitmweb 界面展示

在浏览器访问 http://localhost:8081,可以看到:

复制代码
┌──────────────────────────────────────────────────────────┐
│ mitmweb - HTTP(S) Traffic Inspector                     │
├──────────────────────────────────────────────────────────┤
│ POST https://dashscope.aliyuncs.com/compatible-mode/... │
│ ├─ Request                                               │
│ │  ├─ Headers                                            │
│ │  │  ├─ Authorization: Bearer sk-xxx                    │
│ │  │  └─ Content-Type: application/json                  │
│ │  └─ Body (JSON)                                        │
│ │     {                                                   │
│ │       "model": "qwen-plus",                            │
│ │       "messages": [...],                               │
│ │       "enable_thinking": true  ⬅️ 可以清楚看到        │
│ │     }                                                   │
│ └─ Response                                              │
│    └─ Body (JSON)                                        │
│       {                                                   │
│         "choices": [...],                                │
│         "usage": {...}                                   │
│       }                                                   │
└──────────────────────────────────────────────────────────┘

实际应用:验证 extraBody 问题

测试目的

验证 Spring AI 1.1.2 的 OpenAiChatOptions.extraBody() 是否将 enable_thinking 参数正确传递到 HTTP 请求中。

测试代码

java 复制代码
@Test
void testEnableThinkingWithExtraBody_UserScenario() {
    OpenAiChatOptions options = OpenAiChatOptions.builder()
        .model("qwen-plus")
        .extraBody(Map.of("enable_thinking", true))  // 期望传递这个参数
        .streamUsage(false)
        .build();

    OpenAiApi openAiApi = OpenAiApi.builder()
        .baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
        .completionsPath("/chat/completions")
        .apiKey(apiKey)
        .build();

    OpenAiChatModel chatModel = OpenAiChatModel.builder()
        .openAiApi(openAiApi)
        .defaultOptions(options)
        .build();

    ChatResponse response = chatModel.call(
        new Prompt(new UserMessage("请分析一下1+1等于几?"))
    );
}

抓包结果

通过 mitmproxy 查看实际的 HTTP 请求体:

json 复制代码
{
  "model": "qwen-plus",
  "messages": [
    {
      "role": "user",
      "content": "请分析一下1+1等于几?"
    }
  ],
  "stream": false,
  "streamUsage": false
  // ❌ 注意:没有 "enable_thinking" 字段!
}

结论

  • extraBodyOpenAiChatOptions 对象中正确配置
  • ❌ 但 extraBody 的内容没有出现在实际的 HTTP 请求 JSON 中
  • ❌ 这证实了 Spring AI 1.1.2 的 OpenAiApi 存在 bug(issue #3990)

安全注意事项

为什么要安装证书?

  1. 遵循安全最佳实践:即使是调试,也应该使用正确的证书验证
  2. 避免全局禁用 SSL:禁用 SSL 会影响所有 HTTPS 连接,存在安全风险
  3. 证书可控:只信任 mitmproxy CA,不影响其他证书验证

生产环境警告

⚠️ 永远不要在生产环境中

  • 安装 mitmproxy 证书
  • 禁用 SSL 证书验证
  • 使用中间人代理

这些操作仅用于开发和调试

卸载证书

调试完成后,可以删除 mitmproxy 证书:

bash 复制代码
sudo /opt/homebrew/opt/openjdk@17/bin/keytool -delete \
    -alias mitmproxy \
    -keystore /opt/homebrew/opt/openjdk@17/libexec/openjdk.jdk/Contents/Home/lib/security/cacerts \
    -storepass changeit

总结

问题回顾

  1. 原始问题:使用 mitmproxy 抓包时报证书验证错误
  2. 根本原因:Java 不信任 mitmproxy 的自签名 CA 证书
  3. 失败尝试:代码中禁用 SSL 验证对 Spring RestClient 无效
  4. 正确方案:将 mitmproxy CA 证书安装到 Java cacerts 信任库

技术要点

  1. HTTPS 验证机制:客户端必须信任服务器证书的签发者
  2. 中间人代理原理:mitmproxy 通过动态生成证书实现流量拦截
  3. 双向加密连接:客户端 ↔ mitmproxy 和 mitmproxy ↔ 服务器是两个独立的 TLS 连接
  4. 证书信任链:Java 通过 cacerts 信任库验证证书链的完整性

完整请求路径

复制代码
Java 应用
   ↓ (1) 配置代理,连接 mitmproxy
mitmproxy (127.0.0.1:8080)
   ↓ (2) TLS 握手,出示动态生成的证书
Java 验证证书
   ↓ (3) 在 cacerts 中找到 mitmproxy CA,验证通过
建立加密连接 1
   ↓ (4) 发送加密的 HTTP 请求
mitmproxy 解密请求
   ↓ (5) 记录明文请求内容(可在 mitmweb 查看)
mitmproxy 建立到真实服务器的连接
   ↓ (6) TLS 握手,验证服务器真实证书
建立加密连接 2
   ↓ (7) 转发请求到真实服务器
真实服务器 (dashscope.aliyuncs.com)
   ↓ (8) 处理请求,返回响应
mitmproxy 收到响应
   ↓ (9) 解密并记录响应内容
   ↓ (10) 加密后转发给 Java 应用
Java 应用收到响应
   ↓ (11) 解密并处理
应用程序完成请求

实用价值

通过这个过程,我们:

  • ✅ 成功配置了 HTTPS 抓包环境
  • ✅ 理解了 HTTPS 证书验证机制
  • ✅ 掌握了 mitmproxy 的工作原理
  • ✅ 验证了 Spring AI 的 extraBody bug
  • ✅ 为调试 API 集成问题提供了强大工具

参考资料


作者注 :本文基于实际调试 Spring AI 与 DashScope API 集成时遇到的问题编写,所有命令和代码均已验证可用。
欢迎关注,一起学习,一起进步~

相关推荐
ehiway2 小时前
AI芯片技术演进的双轨路径:从通用架构到领域专用的并行演进——指令集优化与电路级重构协同塑造智能计算新生态
人工智能
没学上了2 小时前
Vlm-vit模型
人工智能
沛沛老爹2 小时前
Web开发者转型AI:Agent Skills版本控制与管理实战——从Git到AI技能仓库
java·前端·人工智能·git·架构·rag
李莫若2 小时前
2026权威评测AI学术写作工具全面对比:AIPaperGPT以一站式服务与强保障体系成为全能冠军
人工智能
weixin_462446232 小时前
使用 Chainlit +langchain+ LangGraph + MCP + Ollama 构建可视化 AI 工具 Agent(完整实战)
人工智能·langchain·agent·ai聊天·mcp server
小郭团队2 小时前
1_5_五段式SVPWM (传统算法反正切+DPWM1)算法理论与 MATLAB 实现详解
人工智能·嵌入式硬件·算法·dsp开发
有Li3 小时前
DACG:用于放射学报告生成的双重注意力和上下文引导模型/文献速递-基于人工智能的医学影像技术
论文阅读·人工智能·文献·医学生
时间会给答案scidag3 小时前
Spring AI Alibaba 学习day01
人工智能·学习·spring
ghie90903 小时前
基于粒子滤波的多目标检测前跟踪(TBD)MATLAB实现
人工智能·目标检测·matlab