简介:
我正在尝试用 Java 编写对公共 API 的调用(它不是已知的 API,但该域是公共的,可以从任何具有互联网访问权限的计算机进行访问)。 为了进行调用,API 所有者向我发送了 openAPi (v3.0.1) 规范。 由于维护/工业化问题,决定使用 openapi-generator-maven-plugin (v7.5.0) 通过 openAPI 规范自动生成客户端。 该插件默认使用 OkHttp3 来生成客户端类。 我必须调用的 API 有一个特点,它经过编程,因此必须发送客户端证书进行身份验证。 如果我从具有直接访问权限的环境中进行调用,则它可以正常工作。 如果我从需要通过 HTTP 代理的环境进行调用,它将无法正常工作。
配置(因保密问题更换变量):
certConf:
apiKey: {API_KEY}
urlService: {URL_SERVICE}
keyStorePath: ".\\src\\main\\resources\\{CLIENT_CERTIFICATE}.pfx"
keyStorePassword: {CLIENT_CERTIFICATE_PASSWORD}
keyStoreType: "PKCS12"
certificatValidePath: ".\\src\\main\\resources\\{API_CERTIFICATE_CHAIN}.crt"
proxy:
externe:
port: 8080
address: {PROXY_IP}
直接致电:
如果我直接调用它就可以正常工作 ->
package {MY_CONF_PACKAGE};
import java.io.FileInputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import {MY_GENERATED_SOURCES_PACKAGE}.ApiInterface;
import {MY_GENERATED_SOURCES_PACKAGE}.ApiClient;
import {MY_CONF_PACKAGE}.CertConf;
import {MY_CONF_PACKAGE}.ProxyConf;
import okhttp3.OkHttpClient;
@Configuration
public class MyClientConf {
@Autowired
private CertConf certConf;
@Autowired
private ProxyConf proxyConf;
@Bean
public ApiInterface apiInterfaceController() throws UnrecoverableKeyException, KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
var apiInterface = new ApiInterface();
apiInterface.setCustomBaseUrl(certConf.getUrlService());
apiInterface.setApiClient(apiClient());
return apiInterface;
}
@Bean
public ApiClient apiClient() throws UnrecoverableKeyException, KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
var apiClient = new ApiClient();
// apiClient.setHttpClient(new OkHttpClient.Builder().proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyConf.getExterne().getAddress(), proxyConf.getExterne().getPort())))
// .build());
apiClient.setKeyManagers(initKeyManager(certConf.getKeyStorePath(), certConf.getKeyStorePassword(), certConf.getKeyStoreType()));
apiClient.setSslCaCert(new FileInputStream(certConf.getCertificatValidePath()));
apiClient.setApiKey(certConf.getApiKey());
return apiClient;
}
private KeyManager[] initKeyManager(String keyStorePath, String keyStorePassword, String keyStoreType) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException, UnrecoverableKeyException {
try(var keyStoreFile = new FileInputStream(keyStorePath)) {
var keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(keyStoreFile, keyStorePassword.toCharArray());
var keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, keyStorePassword.toCharArray());
return keyManagerFactory.getKeyManagers();
}
}
}
此行正确完成其工作(我设法加密连接,以便它是 HTTPS,表明我调用的 API 的 {API_CERTIFICATE_CHAIN} 是可信的)->
apiClient.setSslCaCert(new FileInputStream(certConf.getCertificatValidePath()));
如果配置不正确,我会收到以下错误 ->
PKIX 路径构建失败
此行正确完成其工作(我设法通过发送 {CLIENT_CERTIFICATE} 来通过服务器身份验证屏障)->
apiClient.setKeyManagers(initKeyManager(certConf.getKeyStorePath(), certConf.getKeyStorePassword(), certConf.getKeyStoreType()));
如果配置不正确,我会收到以下错误 ->
403:禁止:缺少证书
通过 HTTP 代理调用不起作用:
如果我通过代理拨打电话,它会部分工作 ->
package {MY_CONF_PACKAGE};
import java.io.FileInputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import {MY_GENERATED_SOURCES_PACKAGE}.ApiInterface;
import {MY_GENERATED_SOURCES_PACKAGE}.ApiClient;
import {MY_CONF_PACKAGE}.CertConf;
import {MY_CONF_PACKAGE}.ProxyConf;
import okhttp3.OkHttpClient;
@Configuration
public class MyClientConf {
@Autowired
private CertConf certConf;
@Autowired
private ProxyConf proxyConf;
@Bean
public ApiInterface apiInterfaceController() throws UnrecoverableKeyException, KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
var apiInterface = new ApiInterface();
apiInterface.setCustomBaseUrl(certConf.getUrlService());
apiInterface.setApiClient(apiClient());
return apiInterface;
}
@Bean
public ApiClient apiClient() throws UnrecoverableKeyException, KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
var apiClient = new ApiClient();
apiClient.setHttpClient(new OkHttpClient.Builder().proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyConf.getExterne().getAddress(), proxyConf.getExterne().getPort())))
.build());
apiClient.setKeyManagers(initKeyManager(certConf.getKeyStorePath(), certConf.getKeyStorePassword(), certConf.getKeyStoreType()));
apiClient.setSslCaCert(new FileInputStream(certConf.getCertificatValidePath()));
apiClient.setApiKey(certConf.getApiKey());
return apiClient;
}
private KeyManager[] initKeyManager(String keyStorePath, String keyStorePassword, String keyStoreType) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException, UnrecoverableKeyException {
try(var keyStoreFile = new FileInputStream(keyStorePath)) {
var keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(keyStoreFile, keyStorePassword.toCharArray());
var keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, keyStorePassword.toCharArray());
return keyManagerFactory.getKeyManagers();
}
}
}
此行正确完成其工作(我设法加密连接,以便它是 HTTPS,表明我调用的 API 的 {API_CERTIFICATE_CHAIN} 是可信的)->
apiClient.setSslCaCert(new FileInputStream(certConf.getCertificatValidePath()));
如果配置不正确,我会收到以下错误 ->
PKIX 路径构建失败
此行未正确完成其工作(我无法通过发送 {CLIENT_CERTIFICATE} 来设法通过服务器身份验证屏障)->
apiClient.setKeyManagers(initKeyManager(certConf.getKeyStorePath(), certConf.getKeyStorePassword(), certConf.getKeyStoreType()));
我收到以下错误 ->
403:禁止:缺少证书
这条线完成了部分工作 ->
apiClient.setHttpClient(new OkHttpClient.Builder().proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyConf.getExterne().getAddress(), proxyConf.getExterne().getPort())))
.build());
我设法通过代理进行呼叫,因为如果配置不正确,我会收到以下错误 ->
连接超时
问题:
我得出的结论是,问题的根源是 HTTP 代理没有正确重定向 {CLIENT_CERTIFICATE}。代理和密钥管理器之间存在我无法理解的交互。
注:
问题不是由流和 HTTP 代理的配置引起的(我已经使用 POSTMAN 进行了调用,无论是否使用 HTTP 代理,它都能正常工作)。 我使用 HTTP 代理调用的环境无法直接访问 API,并且与我直接调用的环境不同。
提前致谢
我已经解决了问题,但部分解决了。如果我使用 JDK 和更现代版本的 java 编译并启动应用程序,它就可以工作。这解决了问题,但我不明白到底是什么解决了它,因为该项目是用 Java 11 制作的,除了这个 HTTP 代理错误之外,它可以正常编译和工作。
JDK11 -> 除了与 HTTP 的奇怪交互之外,所有项目都正常工作
Proxy JDK17 -> 解决HTTP Proxy的错误
这是一个问题,因为迁移应用程序并非易事。