简体   繁体   English

Java SSLException:证书中的主机名不匹配

[英]Java SSLException: hostname in certificate didn't match

I have been using the following code to connect to one of google's service.我一直在使用以下代码连接到谷歌的服务之一。 This code worked fine on my local machine :这段代码在我的本地机器上运行良好:

HttpClient client=new DefaultHttpClient();
HttpPost post = new HttpPost("https://www.google.com/accounts/ClientLogin");
post.setEntity(new UrlEncodedFormEntity(myData));
HttpResponse response = client.execute(post);

I put this code in a production environment, which had blocked Google.com.我把这段代码放在一个生产环境中,该环境已经屏蔽了 Google.com。 On request, they allowed communication with Google server by allowing me to accessing an IP : 74.125.236.52 - which is one of Google's IPs.应要求,他们允许我访问 IP:74.125.236.52 - 这是谷歌的 IP 之一,从而允许与谷歌服务器通信。 I edited my hosts file to add this entry too.我编辑了我的主机文件以添加此条目。

Still I could not access the URL, which I wonder why.我仍然无法访问该 URL,我想知道为什么。 So I replaced the above code with :所以我用以下代码替换了上面的代码:

HttpPost post = new HttpPost("https://74.125.236.52/accounts/ClientLogin");

Now I get an error like this :现在我收到这样的错误:

javax.net.ssl.SSLException: hostname in certificate didn't match: <74.125.236.52> != <www.google.com> javax.net.ssl.SSLException:证书中的主机名不匹配:<74.125.236.52> != <www.google.com>

I guess this is because Google has multiple IPs.我猜这是因为谷歌有多个 IP。 I cant ask the network admin to allow me access to all those IPs - I may not even get this entire list.我不能要求网络管理员允许我访问所有这些 IP - 我什至可能无法获得整个列表。

What should I do now ?我现在该怎么办 ? Is there a workaround at Java level ?在 Java 级别有解决方法吗? Or is it totally in hands of the network guy ?还是完全掌握在网络人手中?

You can also try to set a HostnameVerifier as described here .您还可以尝试按照此处所述设置 HostnameVerifier。 This worked for me to avoid this error.这对我有用,可以避免这个错误。

// Do not do this in production!!!
HostnameVerifier hostnameVerifier = org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER;

DefaultHttpClient client = new DefaultHttpClient();

SchemeRegistry registry = new SchemeRegistry();
SSLSocketFactory socketFactory = SSLSocketFactory.getSocketFactory();
socketFactory.setHostnameVerifier((X509HostnameVerifier) hostnameVerifier);
registry.register(new Scheme("https", socketFactory, 443));
SingleClientConnManager mgr = new SingleClientConnManager(client.getParams(), registry);
DefaultHttpClient httpClient = new DefaultHttpClient(mgr, client.getParams());

// Set verifier     
HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier);

// Example send http request
final String url = "https://encrypted.google.com/";  
HttpPost httpPost = new HttpPost(url);
HttpResponse response = httpClient.execute(httpPost);

The certificate verification process will always verify the DNS name of the certificate presented by the server, with the hostname of the server in the URL used by the client.证书验证过程将始终验证服务器提供的证书的 DNS 名称,以及客户端使用的 URL 中的服务器主机名。

The following code以下代码

HttpPost post = new HttpPost("https://74.125.236.52/accounts/ClientLogin");

will result in the certificate verification process verifying whether the common name of the certificate issued by the server, ie www.google.com matches the hostname ie 74.125.236.52 .将导致证书验证过程验证服务器颁发的证书的通用名称,即www.google.com匹配主机名,即74.125.236.52 Obviously, this is bound to result in failure (you could have verified this by browsing to the URL https://74.125.236.52/accounts/ClientLogin with a browser, and seen the resulting error yourself).显然,这必然会导致失败(您可以通过使用浏览器浏览到 URL https://74.125.236.52/accounts/ClientLogin来验证这一点,并亲自看到导致的错误)。

Supposedly, for the sake of security, you are hesitant to write your own TrustManager (and you musn't unless you understand how to write a secure one), you ought to look at establishing DNS records in your datacenter to ensure that all lookups to www.google.com will resolve to 74.125.236.52 ;据说,为了安全起见,您对编写自己的TrustManager犹豫不决(除非您了解如何编写一个安全的信任管理器,否则您不能这样做),您应该考虑在您的数据中心建立 DNS 记录以确保所有查找到www.google.com将解析为74.125.236.52 this ought to be done either in your local DNS servers or in the hosts file of your OS;这应该在您的本地 DNS 服务器或您的操作系统的hosts文件中完成; you might need to add entries to other domains as well.您可能还需要向其他域添加条目。 Needless to say, you will need to ensure that this is consistent with the records returned by your ISP.不用说,您需要确保这与您的 ISP 返回的记录一致。

I had similar problem.我有类似的问题。 I was using Android's DefaultHttpClient.我使用的是 Android 的 DefaultHttpClient。 I have read that HttpsURLConnection can handle this kind of exception.我读过 HttpsURLConnection 可以处理这种异常。 So I created custom HostnameVerifier which uses the verifier from HttpsURLConnection.所以我创建了自定义 HostnameVerifier,它使用来自 HttpsURLConnection 的验证器。 I also wrapped the implementation to custom HttpClient.我还将实现包装到自定义 HttpClient。

public class CustomHttpClient extends DefaultHttpClient {

public CustomHttpClient() {
    super();
    SSLSocketFactory socketFactory = SSLSocketFactory.getSocketFactory();
    socketFactory.setHostnameVerifier(new CustomHostnameVerifier());
    Scheme scheme = (new Scheme("https", socketFactory, 443));
    getConnectionManager().getSchemeRegistry().register(scheme);
}

Here is the CustomHostnameVerifier class:这是 CustomHostnameVerifier 类:

public class CustomHostnameVerifier implements org.apache.http.conn.ssl.X509HostnameVerifier {

@Override
public boolean verify(String host, SSLSession session) {
    HostnameVerifier hv = HttpsURLConnection.getDefaultHostnameVerifier();
    return hv.verify(host, session);
}

@Override
public void verify(String host, SSLSocket ssl) throws IOException {
}

@Override
public void verify(String host, X509Certificate cert) throws SSLException {

}

@Override
public void verify(String host, String[] cns, String[] subjectAlts) throws SSLException {

}

} }

A cleaner approach ( only for test environment) in httpcliet4.3.3 is as follows. httpcliet4.3.3 中更简洁的方法(仅用于测试环境)如下。

SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext,SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);

CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();

In httpclient-4.3.3.jar, there is another HttpClient to use:在 httpclient-4.3.3.jar 中,还有另一个 HttpClient 可以使用:

public static void main (String[] args) throws Exception {
    // org.apache.http.client.HttpClient client = new DefaultHttpClient();
    org.apache.http.client.HttpClient client = HttpClientBuilder.create().build();
    System.out.println("HttpClient = " + client.getClass().toString());
    org.apache.http.client.methods.HttpPost post = new HttpPost("https://www.rideforrainbows.org/");
    org.apache.http.HttpResponse response = client.execute(post);
    java.io.InputStream is = response.getEntity().getContent();
    java.io.BufferedReader rd = new java.io.BufferedReader(new java.io.InputStreamReader(is));
    String line;
    while ((line = rd.readLine()) != null) { 
        System.out.println(line);
    }
}

This HttpClientBuilder.create().build() will return org.apache.http.impl.client.InternalHttpClient .这个HttpClientBuilder.create().build()将返回org.apache.http.impl.client.InternalHttpClient It can handle the this hostname in certificate didn't match issue.它可以处理证书中的此主机名不匹配问题。

Thanks Vineet Reynolds.谢谢维尼特雷诺兹。 The link you provided held a lot of user comments - one of which I tried in desperation and it helped.您提供的链接包含了很多用户评论——我在绝望中尝试了其中之一,但它有所帮助。 I added this method :我添加了这个方法:

// Do not do this in production!!!
HttpsURLConnection.setDefaultHostnameVerifier( new HostnameVerifier(){
    public boolean verify(String string,SSLSession ssls) {
        return true;
    }
});

This seems fine for me now, though I know this solution is temporary.现在这对我来说似乎很好,尽管我知道这个解决方案是暂时的。 I am working with the network people to identify why my hosts file is being ignored.我正在与网络人员一起确定为什么我的主机文件被忽略。

The concern is we should not use ALLOW_ALL_HOSTNAME_VERIFIER.问题是我们不应该使用 ALLOW_ALL_HOSTNAME_VERIFIER。

How about I implement my own hostname verifier?我如何实现自己的主机名验证程序?

class MyHostnameVerifier implements org.apache.http.conn.ssl.X509HostnameVerifier
{
    @Override
    public boolean verify(String host, SSLSession session) {
        String sslHost = session.getPeerHost();
        System.out.println("Host=" + host);
        System.out.println("SSL Host=" + sslHost);    
        if (host.equals(sslHost)) {
            return true;
        } else {
            return false;
        }
    }

    @Override
    public void verify(String host, SSLSocket ssl) throws IOException {
        String sslHost = ssl.getInetAddress().getHostName();
        System.out.println("Host=" + host);
        System.out.println("SSL Host=" + sslHost);    
        if (host.equals(sslHost)) {
            return;
        } else {
            throw new IOException("hostname in certificate didn't match: " + host + " != " + sslHost);
        }
    }

    @Override
    public void verify(String host, X509Certificate cert) throws SSLException {
        throw new SSLException("Hostname verification 1 not implemented");
    }

    @Override
    public void verify(String host, String[] cns, String[] subjectAlts) throws SSLException {
        throw new SSLException("Hostname verification 2 not implemented");
    }
}

Let's test against https://www.rideforrainbows.org/ which is hosted on a shared server.让我们针对托管在共享服务器上的https://www.rideforrainbows.org/进行测试。

public static void main (String[] args) throws Exception {
    //org.apache.http.conn.ssl.SSLSocketFactory sf = org.apache.http.conn.ssl.SSLSocketFactory.getSocketFactory();
    //sf.setHostnameVerifier(new MyHostnameVerifier());
    //org.apache.http.conn.scheme.Scheme sch = new Scheme("https", 443, sf);

    org.apache.http.client.HttpClient client = new DefaultHttpClient();
    //client.getConnectionManager().getSchemeRegistry().register(sch);
    org.apache.http.client.methods.HttpPost post = new HttpPost("https://www.rideforrainbows.org/");
    org.apache.http.HttpResponse response = client.execute(post);
    java.io.InputStream is = response.getEntity().getContent();
    java.io.BufferedReader rd = new java.io.BufferedReader(new java.io.InputStreamReader(is));
    String line;
    while ((line = rd.readLine()) != null) { 
        System.out.println(line);
    }
}

SSLException: SSL异常:

Exception in thread "main" javax.net.ssl.SSLException: hostname in certificate didn't match: www.rideforrainbows.org != stac.rt.sg OR stac.rt.sg OR www.stac.rt.sg线程“main”中的异常 javax.net.ssl.SSLException:证书中的主机名不匹配:www.rideforrainbows.org != stac.rt.sg OR stac.rt.sg OR www.stac.rt.sg
at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:231)在 org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:231)
... ...

Do with MyHostnameVerifier:使用 MyHostnameVerifier:

public static void main (String[] args) throws Exception {
    org.apache.http.conn.ssl.SSLSocketFactory sf = org.apache.http.conn.ssl.SSLSocketFactory.getSocketFactory();
    sf.setHostnameVerifier(new MyHostnameVerifier());
    org.apache.http.conn.scheme.Scheme sch = new Scheme("https", 443, sf);

    org.apache.http.client.HttpClient client = new DefaultHttpClient();
    client.getConnectionManager().getSchemeRegistry().register(sch);
    org.apache.http.client.methods.HttpPost post = new HttpPost("https://www.rideforrainbows.org/");
    org.apache.http.HttpResponse response = client.execute(post);
    java.io.InputStream is = response.getEntity().getContent();
    java.io.BufferedReader rd = new java.io.BufferedReader(new java.io.InputStreamReader(is));
    String line;
    while ((line = rd.readLine()) != null) { 
        System.out.println(line);
    }
}

Shows:节目:

Host=www.rideforrainbows.org主机=www.rideforrainbows.org
SSL Host=www.rideforrainbows.org SSL 主机=www.rideforrainbows.org

At least I have the logic to compare (Host == SSL Host) and return true.至少我有比较(Host == SSL Host)并返回true的逻辑。

The above source code is working for httpclient-4.2.3.jar and httpclient-4.3.3.jar.上述源代码适用于 httpclient-4.2.3.jar 和 httpclient-4.3.3.jar。

将 java 版本从 1.8.0_40 更新到 1.8.0_181 解决了这个问题。

            SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(
                    SSLContexts.custom().loadTrustMaterial(null, new TrustSelfSignedStrategy()).build(),
                    SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);

            CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(sslConnectionSocketFactory).build();

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 Java SSLException:证书中的主机名在Android应用程序中不匹配 - Java SSLException: hostname in certificate didn't match in android application SSLException:证书中的主机名不匹配:&lt;50.19.233.255&gt;!= &lt;*。heroku.com&gt; - SSLException: hostname in certificate didn't match: <50.19.233.255> != <*.heroku.com> javax.net.ssl.SSLException:证书中的主机名不匹配 - javax.net.ssl.SSLException: hostname in certificate didn't match Java SSLException:证书中的主机名与 www.googleapis.com 不匹配 - Java SSLException: hostname in certificate didn't match for www.googleapis.com 使用HTTPS Confusion的Java HTTP帖子 - javax.net.ssl.SSLException:证书中的主机名不匹配 - Java HTTP post using HTTPS Confusion - javax.net.ssl.SSLException: hostname in certificate didn't match javax.net.ssl.SSLException:证书中的主机名与Apache httpd虚拟主机不匹配 - javax.net.ssl.SSLException: hostname in certificate didn't match in conjunction with Apache httpd virtual hosts RestEasy的客户端实现导致javax.net.ssl.SSLException:证书中的主机名不匹配 - RestEasy's Client implementation causes javax.net.ssl.SSLException: hostname in certificate didn't match 间接Apache HttpClient和javax.net.ssl.SSLException:证书中的主机名不匹配 - Indirect Apache HttpClient and javax.net.ssl.SSLException: hostname in certificate didn't match 证书中的主机名不匹配 - Hostname in certificate didn't match 证书中的主机名不匹配? - Hostname in certificate didn't match?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM