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

时间:2011-08-31 12:34:02

标签: java ssl https

我一直在使用以下代码连接到Google的一项服务。这段代码在我的本地机器上运行良好:

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

我将此代码放在生产环境中,该环境已阻止Google.com。根据要求,他们允许我访问IP:74.125.236.52(这是Google的IP之一),允许与Google服务器进行通信。我编辑了我的hosts文件以添加此条目。

我还是无法访问网址,我想知道为什么。所以我用以下代码替换了上面的代码:

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

现在我收到这样的错误:

  

javax.net.ssl.SSLException:证书中的主机名不匹配:   < 74.125.236.52> !=< www.google.com>

我想这是因为Google有多个IP。我不能让网络管理员允许我访问所有这些IP - 我甚至可能没有得到这整个列表。

我现在该怎么办? Java级别有解决方法吗?或者它完全掌握在网络人手中?

8 个答案:

答案 0 :(得分:27)

您还可以尝试按照here所述设置HostnameVerifier。这对我有用,可以避免这个错误。

// 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);

答案 1 :(得分:22)

证书验证过程将始终验证服务器提供的证书的DNS名称,以及客户端使用的URL中服务器的主机名。

以下代码

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

将导致证书验证过程验证服务器发布的证书的公用名,即www.google.com是否与主机名匹配,即74.125.236.52。显然,这肯定会导致失败(您可以通过浏览器浏览到URL https://74.125.236.52/accounts/ClientLogin来验证这一点,并亲自看到结果错误。)

据说,为了安全起见,你在编写自己的TrustManager时犹豫不决(除非你明白如何编写一个安全的版本,否则你不会这样做),你应该考虑在你的网络中建立DNS记录数据中心,以确保www.google.com的所有查找都将解析为74.125.236.52;这应该在您的本地DNS服务器或您的操作系统的hosts文件中完成;您可能还需要将条目添加到其他域。不用说,您需要确保这与您的ISP返回的记录一致。

答案 2 :(得分:13)

我有类似的问题。我使用的是Android的DefaultHttpClient。我已经读过HttpsURLConnection可以处理这种异常。所以我创建了自定义HostnameVerifier,它使用来自HttpsURLConnection的验证器。我还将实现包装到自定义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);
}

以下是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 {

}

}

答案 3 :(得分:8)

httpcliet4.3.3中的一种更清洁的方法(仅适用于测试环境)如下所示。

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

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

答案 4 :(得分:5)

感谢Vineet Reynolds。你提供的链接持有很多用户评论 - 其中一个我在绝望中尝试过并且它有所帮助。我添加了这个方法:

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

这对我来说似乎很好,但我知道这个解决方案是暂时的。我正在与网络人员一起确定我的主机文件被忽略的原因。

答案 5 :(得分:5)

在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);
    }
}

HttpClientBuilder.create()。build()将返回 org.apache.http.impl.client.InternalHttpClient 。它可以处理证书中的主机名匹配问题。

答案 6 :(得分:3)

关注的是我们不应该使用ALLOW_ALL_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");
    }
}

让我们对共享服务器上托管的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:

  

线程“main”中的异常javax.net.ssl.SSLException:证书中的主机名不匹配:www.rideforrainbows.org!= stac.rt.sg或stac.rt.sg或www.stac.rt. SG
    在org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:231)
  ...

使用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);
    }
}

节目:

  

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

至少我有比较的逻辑(Host == SSL Host)并返回true。

以上源代码适用于httpclient-4.2.3.jar和httpclient-4.3.3.jar。

答案 7 :(得分:0)

将Java版本从1.8.0_40更新到1.8.0_181解决了该问题。