次のコードを使用して、Googleのサービスの1つに接続しています。このコードは私のローカルマシンでうまくいきました:
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の1つにアクセスできるようにすることで、Googleサーバーとの通信を許可しました。 hostsファイルを編集して、このエントリも追加しました。
それでもURLにアクセスできませんでしたが、なぜだろうか。したがって、上記のコードを次のように置き換えました。
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 levelで回避策がありますか?それとも、完全にネットワーク担当者の手にありますか?
Vineet Reynoldsに感謝します。あなたが提供したリンクは、多くのユーザーのコメントを保持していました。私はこのメソッドを追加しました:
// Do not do this in production!!!
HttpsURLConnection.setDefaultHostnameVerifier( new HostnameVerifier(){
public boolean verify(String string,SSLSession ssls) {
return true;
}
});
私はこの解決策が一時的なものであることを知っていますが、これは今私にとっては問題ないようです。私は、ネットワークの人々と協力して、hostsファイルが無視される理由を特定しています。
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);
証明書検証プロセスは、サーバーが提示した証明書のDNS名を、クライアントが使用するURLのサーバーのホスト名で常に検証します。
次のコード
HttpPost post = new HttpPost("https://74.125.236.52/accounts/ClientLogin");
サーバーによって発行された証明書の共通名、つまりwww.google.com
がホスト名、つまり74.125.236.52
と一致するかどうかを検証する証明書検証プロセスになります。明らかに、これは失敗に結びつきます(ブラウザーでhttps://74.125.236.52/accounts/ClientLogin
にアクセスして、これを確認し、結果のエラーを自分で確認できます)。
おそらく、セキュリティのために、独自のTrustManager
を書くことをためらっています(そして、安全なものを書く方法を理解しない限り、あなたはデータセンターでDNSレコードを確立することを見てはいけません) www.google.com
へのすべてのルックアップが74.125.236.52
に解決されることを確認してください。これは、ローカルDNSサーバーまたはOSのhosts
ファイルで行う必要があります。他のドメインにもエントリを追加する必要がある場合があります。言うまでもなく、これがISPから返されたレコードと一致していることを確認する必要があります。
同様の問題がありました。 AndroidのDefaultHttpClientを使用していました。 HttpsURLConnectionがこの種の例外を処理できることを読みました。そこで、HttpsURLConnectionのベリファイアを使用するカスタムHostnameVerifierを作成しました。また、実装をカスタム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 {
}
}
Httpcliet4.3.3のクリーンなアプローチ(テスト環境のみ)は次のとおりです。
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext,SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
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を返します。 this 証明書のホスト名が一致しませんでしたの問題を処理できます。
懸念は、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:
スレッド「メイン」の例外javax.net.ssl.SSLException:証明書のホスト名が一致しませんでした:www.rideforrainbows.org!= stac.rt.sg OR stac.rt.sg = OR 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);
}
}
ショー:
Host = www.rideforrainbows.org
SSL Host = www.rideforrainbows.org
少なくとも、比較(Host == SSL Host)してtrueを返すロジックがあります。
上記のソースコードは、httpclient-4.2.3.jarおよびhttpclient-4.3.3.jarで機能します。