编辑:我试图在我的博客上以更得体的方式格式化问题和接受的答案。
这是最初的问题。
我得到这个错误:
详细消息sun.security.validator.ValidatorException: PKIX路径
构建失败:
provider.certpath. suncertpathbuilderexception:不能
找到请求目标的有效认证路径
导致javax.net.ssl.SSLHandshakeException:
validatorexception: PKIX路径构建
失败:sun.security.provider.certpath.SunCertPathBuilderException:
无法找到请求目标的有效认证路径
我使用Tomcat 6作为web服务器。我在同一台机器上的不同端口的不同Tomcats上安装了两个HTTPS web应用程序。假设App1(端口8443)和App2(端口443)。App1连接到App2。当App1连接到App2时,我得到上述错误。我知道这是一个非常常见的错误,所以在不同的论坛和网站上找到了许多解决方案。我在两个Tomcats的server.xml中有以下条目:
keystoreFile="c:/.keystore"
keystorePass="changeit"
每个站点都说明了app2给出的证书不在app1 jvm的可信存储区中的相同原因。这似乎也是真的,当我试图在IE浏览器中点击相同的URL,它工作(与升温,有一个问题与此网站的安全证书。在这里我说继续这个网站)。但是当相同的URL被Java客户端(在我的情况下)击中时,我得到上述错误。为了把它放到信任库中,我尝试了以下三个选项:
选项1
System.setProperty("javax.net.ssl.trustStore", "C:/.keystore");
System.setProperty("javax.net.ssl.trustStorePassword", "changeit");
选项2
在环境变量中设置如下
CATALINA_OPTS -- param name
-Djavax.net.ssl.trustStore=C:\.keystore -Djavax.net.ssl.trustStorePassword=changeit ---param value
选项3
在环境变量中设置如下
JAVA_OPTS -- param name
-Djavax.net.ssl.trustStore=C:\.keystore -Djavax.net.ssl.trustStorePassword=changeit ---param value
结果
但是什么都不管用。
最后工作是执行Java方法建议如何处理无效的SSL证书与Apache HttpClient?通过Pascal Thivent,即执行程序InstallCert。
但这种方法适用于开发盒设置,但我不能在生产环境中使用它。
我想知道为什么上面提到的三种方法都不工作,而我已经通过设置在App2服务器的server.xml中提到了相同的值,在truststore中也提到了相同的值
System.setProperty("javax.net.ssl.trustStore", "C:/.keystore")和System.setProperty("javax.net.ssl.trustStorePassword", "changeit");
在App1程序中。
要了解更多信息,这是我如何建立联系:
URL url = new URL(urlStr);
URLConnection conn = url.openConnection();
if (conn instanceof HttpsURLConnection) {
HttpsURLConnection conn1 = (HttpsURLConnection) url.openConnection();
conn1.setHostnameVerifier(new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
return true;
}
});
reply.load(conn1.getInputStream());
对于OkHttpClient,这个解决方案适合我。
它可能会帮助使用图书馆的人……
try {
String proxyHost = "proxy_host";
String proxyUsername = "proxy_username";
String proxyPassword = "proxy_password";
Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, "port goes here"));
// Create a trust manager that does not validate certificate chains
TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
@Override
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return new java.security.cert.X509Certificate[]{};
}
@Override
public void checkClientTrusted(
java.security.cert.X509Certificate[] certs, String authType) {
}
@Override
public void checkServerTrusted(
java.security.cert.X509Certificate[] certs, String authType) {
}
}
};
// Install the all-trusting trust manager
final SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
// Create an ssl socket factory with our all-trusting manager
final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
OkHttpClient client = new OkHttpClient().newBuilder()
.sslSocketFactory(sslSocketFactory, (X509TrustManager) trustAllCerts[0])
.hostnameVerifier((hostname, session) -> true)
.connectTimeout(timeout, TimeUnit.SECONDS)
.proxy(proxy)
.proxyAuthenticator((route, response) -> {
String credential = Credentials.basic(proxyUsername, proxyPassword);
return response.request().newBuilder()
.header("Proxy-Authorization", credential)
.build();
})
.build();
MediaType mediaType = MediaType.parse("application/json");
RequestBody requestBody = RequestBody.create(payload, mediaType);
Request request = new Request.Builder()
.url(url)
.header("Authorization", "authorization data goes here")
.method(requestMethod, requestBody)
.build();
Response response = client.newCall(request).execute();
resBody = response.body().string();
int responseCode = response.code();
} catch (Exception ex) {
}
可部署解决方案(Alpine Linux)
为了能够在我们的应用程序环境中修复这个问题,我们准备了如下的Linux终端命令:
cd ~
将在主目录中生成证书文件。
apk add openssl
该命令在alpine Linux中安装openssl。您可以找到其他Linux发行版的适当命令。
openssl s_client -connect <host-dns-ssl-belongs> < /dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > public.crt
生成所需的证书文件。
sudo $JAVA_HOME/bin/keytool -import -alias server_name -keystore $JAVA_HOME/lib/security/cacerts -file public.crt -storepass changeit -noprompt
使用'keytool'程序将生成的文件应用到JRE。
注意:请将您的DNS替换为<host- DNS -ssl-belong >
注意:请注意-noprompt不会提示验证信息(yes/no), -storepass changeit参数将禁用密码提示并提供所需的密码(默认为'changeit')。这两个属性将允许您在应用程序环境中使用这些脚本,例如构建Docker映像。
注3:如果你是通过Docker部署你的应用程序,你可以生成一次秘密文件,并把它放在你的应用程序项目文件中。您不需要一次又一次地生成它。
可以通过编程方式禁用SSL验证。对于开发人员来说非常有效,但不推荐用于生产环境,因为您可能需要在那里使用“真正的”SSL验证,或者安装并使用您自己的可信密钥,然后仍然使用“真正的”SSL验证。
下面的代码为我工作:
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.X509TrustManager;
public class TrustAnyTrustManager implements X509TrustManager {
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public X509Certificate[] getAcceptedIssuers() {
return null;
}
}
HttpsURLConnection conn = null;
URL url = new URL(serviceUrl);
conn = (HttpsURLConnection) url.openConnection();
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, new TrustManager[]{new TrustAnyTrustManager()}, new java.security.SecureRandom());
// Create all-trusting host name verifier
HostnameVerifier allHostsValid = new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
return true;
}
};
conn.setHostnameVerifier(allHostsValid);
或者,如果您不控制下面的连接,您也可以为所有连接覆盖全局SSL验证https://stackoverflow.com/a/19542614/32453
如果你正在使用Apache HTTPClient,你必须“以不同的方式”禁用它:https://stackoverflow.com/a/2703233/32453