我正在向我们的大型Java应用程序中添加一个模块,该模块需要与另一家公司的SSL安全网站通信。问题在于该网站使用自签名证书。我有证书副本以验证未遇到中间人攻击,并且我需要将此证书合并到我们的代码中,以便与服务器的连接成功。
以下是基本代码:
void sendRequest(String dataPacket) {
String urlStr = "https://host.example.com/";
URL url = new URL(urlStr);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setMethod("POST");
conn.setRequestProperty("Content-Length", data.length());
conn.setDoOutput(true);
OutputStreamWriter o = new OutputStreamWriter(conn.getOutputStream());
o.write(data);
o.flush();
}
如果没有任何额外的处理来处理自签名证书,这将在 conn.getOutputStream() 处停止并抛出以下异常:
Exception in thread "main" javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
....
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
....
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
理想情况下,我的代码需要教会Java仅接受此应用程序中的一个位置使用的此自签名证书,而不影响其他地方。
我知道可以将证书导入JRE的证书颁发机构存储库,这将允许Java接受它。但如果可能的话,我不想采取这种方法;在所有客户机器上为他们可能不使用的一个模块执行此操作似乎非常具有侵入性;这将影响使用相同JRE的所有其他Java应用程序,并且即使任何其他Java应用程序永远不会访问此站点,我也不喜欢这一点。这也不是一项微不足道的操作:在UNIX上,我必须获取修改JRE权限。
我还发现可以创建一个TrustManager实例来执行一些自定义检查。看起来我甚至可以创建一个TrustManager,在所有情况下都委托给真实的TrustManager,除了这一个证书。但看起来该TrustManager会全局安装,并且我推测会影响我们应用程序的所有其他连接,这对我来说并不合适。
设置Java应用程序以接受自签名证书的首选、标准或最佳方法是什么?我能否实现上述所有目标,还是我必须妥协?是否存在涉及文件、目录和配置设置的选项,以及很少的代码?