我要添加到大型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应用程序以接受自签名证书的首选、标准或最佳方式是什么?我能完成上面提到的所有目标吗,还是必须妥协?是否有一个选项涉及文件、目录和配置设置,几乎没有代码?

推荐答案

自己创建一个SSLSocket工厂,并在连接之前将其设置为HttpsURLConnection.

...
HttpsURLConnection conn = (HttpsURLConnection)url.openConnection();
conn.setSSLSocketFactory(sslFactory);
conn.setMethod("POST");
...

你会想要创建一个SSLSocketFactory并保持它不变.下面是如何初始化它的示意图:

/* Load the keyStore that includes self-signed cert as a "trusted" entry. */
KeyStore keyStore = ... 
TrustManagerFactory tmf = 
  TrustManagerFactory.getInstance(TrustManagerFactory.getDefault算法rithm());
tmf.init(keyStore);
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(null, tmf.getTrustManagers(), null);
sslFactory = ctx.getSocketFactory();

如果您需要帮助创建密钥存储,请发表 comments .


下面是加载密钥存储的示例:

KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(trustStore, trustStorePassword);
trustStore.close();

要使用PEM格式证书创建密钥存储,您可以使用CertificateFactory编写自己的代码,或者只使用keytool从JDK导入它(keytool won't适用于"密钥条目",但适用于"受信任条目").

keytool -import -file selfsigned.pem -alias server -keystore server.jks

Java相关问答推荐

如何在运行docker的应用程序中获取指定的配置文件

Java CDI:@Singleton@Startup@Inject无法实现接口

Java返回生成器的实现

使用StringBuilder和append方法创建字符串时Java字符串内部方法的问题

[Guice/MissingImplementation]:未绑定任何实现

Spring Boot应用程序中的自定义constraintvalidator不会被调用

将 JSF 2.3 应用程序部署到 Tomcat 9.0.80 时出现异常

为什么我得到 java.sql 可能无法被客户端访问,因为缺少需要传递

Leetcode:根据主元对数组进行分区

为什么第二个方法调用会导致java崩溃?

无法在 Spring Boot 中使用自定义请求类中的存储库进行请求验证

如何隐藏提供公共静态工厂方法的 Java 记录上的构造函数?

LWJGL投影矩阵不起作用(即使与投影矩阵相乘后,四边形仍保持不变)

使用数据源连接Oracle数据库的JDBC连接

AWS SES 警告:邮件运行状况判断失败

使用 .tar.gz 存档在 Linux 上安装 JDK

Java高亮函数选中jlist索引名

使用 JPA/Hibernate 将 UUID 保存为数据库中的字符串

Spring 控制器返回音频文件的字节:html 音频播放器无法快进或快退

为什么在 SQL Room 中没有生成密钥?