Android Retrofit主机名未经验证

10

我得到了针对IP地址(不是常见名称)发行的证书,并且正在尝试使用该证书连接到服务器。

OkHttpClient.Builder builder = new OkHttpClient().newBuilder();
OkHttpClient okHttpClient = builder.build();
Gson gson = new GsonBuilder()
                .setLenient()
                .create();
retrofit = new Retrofit.Builder()
                .baseUrl(url)
                .client(okHttpClient)
                .addConverterFactory(GsonConverterFactory.create(gson))
                .build();
ServerRouts service = retrofit.create(ServerRouts.class);
Resp_json> call = service.login(param, user, pw);

然后我遇到了一个错误:

Hostname 11.8.222.333 not verified:

但是当我使用时

builder.hostnameVerifier(new HostnameVerifier() {
                @Override
                public boolean verify(String hostname, SSLSession session) {
                    return true;
                }
            });

那么一切都能正常工作。

如何在不关闭主机名验证程序的情况下解决该错误?

附言:我的证书发给了 IP(11.8.222.333)。


1
请检查一下使用 REST 客户端是否正常工作,如果正常工作,则需要检查 Retrofit 的问题!! - Vishal Patoliya ツ
3个回答

2

我重新定义了verify方法(只是从DefaultHostnameVerifier.java中复制了源代码),现在一切都正常了。我不知道为什么它之前不能工作,但现在没问题了。

builder.hostnameVerifier(new HostnameVerifier() {
            @Override
            public boolean verify(String hostname, SSLSession session) {

                Certificate[] certs;
                try {
                    certs = session.getPeerCertificates();
                } catch (SSLException e) {
                    return false;
                }
                X509Certificate x509 = (X509Certificate) certs[0];
                // We can be case-insensitive when comparing the host we used to
                // establish the socket to the hostname in the certificate.
                String hostName = hostname.trim().toLowerCase(Locale.ENGLISH);
                // Verify the first CN provided. Other CNs are ignored. Firefox, wget,
                // curl, and Sun Java work this way.
                String firstCn = getFirstCn(x509);
                if (matches(hostName, firstCn)) {
                    return true;
                }
                for (String cn : getDNSSubjectAlts(x509)) {
                    if (matches(hostName, cn)) {
                        return true;
                    }
                }
                return false;

            }
        });


private String getFirstCn(X509Certificate cert) {
        String subjectPrincipal = cert.getSubjectX500Principal().toString();
        for (String token : subjectPrincipal.split(",")) {
            int x = token.indexOf("CN=");
            if (x >= 0) {
                return token.substring(x + 3);
            }
        }
        return null;
    }

这里的getFirstCn(x509)方法是什么? - Nainal
1
我从这段代码中得到了一个崩溃:AndroidRuntime: FATAL EXCEPTION: OkHttp Dispatcher Process: com.igor.mobile, PID: 30362 java.lang.NullPointerException: Attempt to get length of null array - IgorGanapolsky

2
OkHttpClient.Builder默认的HostNameVerifierokhttp3.internal.tls.OkHostnameVerifier。看起来,这个验证器不会将主机名与对等方证书中的通用名称匹配,而仅匹配主题备用名称。这种行为是设计上的限制,而非错误——请参阅此问题:https://github.com/square/okhttp/issues/4966 有两个选项可以解决这个问题:
  1. 如果您想继续使用OkHostnameVerifier,您应该确保您的服务器证书包含了主题备用名称(SANs)。至少在SANs中重复通用名称即可。我发现这个gist有助于实现这一点:https://gist.github.com/croxton/ebfb5f3ac143cd86542788f972434c96
  2. 如果您无法控制服务器证书,您可以使用另一种HostNameVerifier实现,该实现还将根据证书的CN进行匹配。@Rainmaker回答中的一个实现https://dev59.com/yVgR5IYBdhLWcg3wO7bX#41543384。我还发现Apache的httpclient包中的org.apache.http.conn.ssl.DefaultHostnameVerifier也可以正常工作,并且可能是一个更详细的实现。
// instantiate and configure your OkHttpClient.Builder and then:
builder.hostnameVerifier(new org.apache.http.conn.ssl.DefaultHostnameVerifier());

0

实际上,这是错误的答案,只是一个短期解决方案,以避免出现错误。 如果您不需要确切的HTTPS,则建议采用以下解决方案:

  1. 使用HTTP而不是HTTPS

  2. 将URL添加到network_security_config.xml文件中,并将其放置在res/xml/目录中

  3. 在清单文件中,将以下属性添加到应用程序标记中

    <application ... android:networkSecurityConfig="@xml/network_security_config" ..>


1
这对于本地开发来说还可以,但我不建议用于生产环境。 - Sabel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接