You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java双向SSL(Mutual TLS)请求返回403,Postman/curl可正常请求

双向SSL(Mutual TLS)SOAP服务器对接问题排查与解决

前置验证(Postman & curl)

  • Postman测试:开启SSL证书验证,添加CA证书和客户端PFX证书后请求成功;移除客户端证书返回HTTP 403,确认客户端证书为必需项。
  • curl测试:将PFX转成P12格式后请求正常执行,证明证书本身有效。

Java端问题表现

  • 仅添加CA证书到信任库时,SSL握手成功,但因缺少客户端证书返回403,符合预期逻辑。
  • 导入同一客户端PFX证书到密钥库(尝试过PKCS12、JKS两种格式)后,仍持续返回403。
  • 开启SSL握手日志后,仅观测到CA证书被发送,无客户端证书参与握手的记录(已确认证书成功加载),推测Java应用未正确使用客户端证书。

测试用极简Java代码

String url = "https://.....";

// 请求体(字符串格式)
String body = "...";

Map<String, String> headers = Map.of(
        "Content-Type", "application/soap+xml; charset=utf-8"
);

KeyStore clientStore = KeyStore.getInstance("PKCS12");
InputStream keyStoreStream = new FileInputStream("client.pfx");
clientStore.load(keyStoreStream, "A123456789".toCharArray());

KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(clientStore, "mypassword".toCharArray());

KeyStore trustStore = KeyStore.getInstance("JKS");
trustStore.load(new FileInputStream("ca_intermediate.jks"), "mypassword".toCharArray());

TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(trustStore);

SSLContext sslContext = SSLContext.getInstance("TLS");
//sslContext.init(kmf.getKeyManagers(), null, new SecureRandom());
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom());

SSLConnectionSocketFactory socketFactory =
        new SSLConnectionSocketFactory(sslContext, new DefaultHostnameVerifier());

Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
        .register("https", socketFactory)
        .build();

PoolingHttpClientConnectionManager connectionManager =
        new PoolingHttpClientConnectionManager(socketFactoryRegistry);

CloseableHttpClient httpClient = HttpClients.custom()
        .setSSLSocketFactory(socketFactory)
        .setConnectionManager(connectionManager)
        .build();


HttpPost post = new HttpPost(url);
post.setEntity(new StringEntity(body));

headers.forEach(post::addHeader);
HttpResponse response = httpClient.execute(post);

System.out.println(response.getStatusLine().getStatusCode());

排查与解决要点

  1. 统一密钥库密码:代码中加载PFX用的密码是"A123456789",但初始化KeyManagerFactory用的是"mypassword",这两个密码必须一致(PFX文件的保护密码和密钥库密码通常为同一值)。密码不匹配会导致KeyManagerFactory无法提取客户端私钥,进而无法发送客户端证书。
  2. 检查证书链完整性:确认客户端PFX文件包含完整证书链(客户端证书+中间CA证书)。若只有客户端证书,Java无法构建有效证书链,服务器会拒绝接受。可通过命令keytool -list -v -keystore client.pfx -storetype PKCS12查看证书链详情。
  3. 指定兼容的TLS版本:部分服务器仅支持特定TLS版本(如TLSv1.2),可将SSLContext.getInstance("TLS")改为SSLContext.getInstance("TLSv1.2"),避免Java默认版本与服务器不兼容。
  4. 验证密钥库证书别名:用keytool列出密钥库中的证书别名,确认客户端证书存在且关联私钥。若密钥库中有多个证书,可在KeyManagerFactory初始化时指定目标别名。
  5. 排除连接池缓存问题:尝试不使用PoolingHttpClientConnectionManager,直接构建HttpClient测试,避免连接池缓存未加载客户端证书的旧连接。
  6. 临时验证主机名匹配:若服务器证书的CN/SAN与请求域名不匹配,可临时替换DefaultHostnameVerifier为NoopHostnameVerifier测试(生产环境禁用此操作)。

修正后的核心代码片段(针对密码不一致问题)

// 统一PFX加载和KeyManagerFactory初始化的密码
String pfxPassword = "A123456789";

KeyStore clientStore = KeyStore.getInstance("PKCS12");
InputStream keyStoreStream = new FileInputStream("client.pfx");
clientStore.load(keyStoreStream, pfxPassword.toCharArray());

KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(clientStore, pfxPassword.toCharArray());

内容的提问来源于stack exchange,提问作者Mark

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 02:13:10