我正在开发部署在 Azure App Service 实例上的 Java Web 应用程序。我需要调用一个 REST API,该 API 需要通过 SSL 进行相互身份验证来保护该 API。由于这是一个应用程序服务,我没有将证书和公钥分别添加到密钥库和信任库的奢侈,而这一切都必须通过代码完成。尽管使用 JCE 和 SSL,我还是设法编写了以下控制台应用程序,成功访问了安全 API(在其他 StackOverflow Q&A 的帮助下):
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
public class TestPFOM {
public static void main(String[] args) throws KeyStoreException, NoSuchAlgorithmException, CertificateException,
IOException, UnrecoverableKeyException, KeyManagementException {
System.out.println("Start test for mutual authentication");
KeyStore ks = KeyStore.getInstance("PKCS12");
File file = new File(System.getProperty("user.dir") + "/client.company.com.pfx");
System.out.println("Loaded PKCS12 from file");
try (FileInputStream fis = new FileInputStream(file)) {
ks.load(fis, "password".toCharArray());
System.out.println("Loaded keys into keystore");
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(ks, "password".toCharArray());
System.out.println("Initialized KeyStoreManager");
SSLContext sc = SSLContext.getInstance("TLS");
sc.init(kmf.getKeyManagers(), null, new SecureRandom());
System.out.println("initialized SSLContext");
SSLSocketFactory factory = sc.getSocketFactory();
System.out.println("Obtained SSLSocketFactory");
URL url = new URL("https://services.company.com/api/company_data");
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
System.out.println("Opened secure HTTPS connection");
connection.setSSLSocketFactory(factory);
connection.setRequestMethod("GET");
connection.setRequestProperty("Accept", "application/json");
StringBuilder stringBuilder = new StringBuilder();
int responseCode = connection.getResponseCode();
System.out.println("HTTP response code = " + responseCode);
try (BufferedReader reader = responseCode == 200
? new BufferedReader(new InputStreamReader(connection.getInputStream()))
: new BufferedReader(new InputStreamReader(connection.getErrorStream()))) {
String line = null;
while ((line = reader.readLine()) != null) {
System.out.println(line);
stringBuilder.append(line);
}
} catch (Exception e) {
System.err.println("Error: " + e.getMessage());
}
System.out.println(stringBuilder.toString());
} catch (Exception ex) {
System.err.println("Error: " + ex.getMessage());
}
}
}
我需要从已存储证书的 Azure Keyvault 获取证书,而不是将 PFX 文件加载到 KeyStore 中。KeyVaultClient(来自 Azure 的 Java 客户端库)为我提供了一种获取 X509Certificate 对象的机制。是否可以使用 X509Certificate 对象而不是从 PFX 文件启动 KeyStore?
我的目标是为请求处理机制提供一个可重用的 SSLContext 对象,这样当我的 Web 应用程序接收到请求时,我可以使用它来调用外部的安全 API。而且我需要在不依赖文件系统中的任何文件和外部 JVM 密钥/信任存储的情况下执行此操作。
2018 年 7 月 5 日:跟进 GPI 的富有洞察力的建议,我手动构建了SSLContext
:
KeyStore keyStore = KeyStore.getInstance("PKCS12");
// Initiate and load empty key store
keyStore.load(null, null);
// clientCert is an X509Certificate object
keyStore.setCertificateEntry("clientCert", clientCert);
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); // PKIX
trustManagerFactory.init(keyStore);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
但是当我使用生成SSLSocketFactory
的 HTTPS 连接时,我收到以下错误:
sun.security.validator.ValidatorException:
PKIX path building failed:
sun.security.provider.certpath.SunCertPathBuilderException:
unable to find valid certification path to requested target