8

编辑:抱歉,如果我的原始帖子措辞不佳。它导致了一些混乱,以对原始帖子的评论为代表。所以让我再试一次:

我从一个问题开始。我想解决 Android 上的问题,但不知道如何解决。我花了很多时间在网上寻找解决方案,但在任何地方都没有发现任何关于此事的讨论。尽管如此,包括 StackOverflow 线程在内的一些讨论让我找到了一种看起来很有前途的技术。我解决了这个问题。但是解决方案有点复杂。所以我决定在这里发布问题,认为a)一定有更好的解决方案,希望有人知道并在这里发布答案;或b)也许这是一个很好的解决方案,并且由于我在网络上的其他任何地方都没有发现有关此问题的讨论,因此我对该问题的解决方案可能对尝试做同样事情的其他人有用。无论哪种方式,结果都是对 StackOverflow 的新贡献:一个在其他地方没有回答的问题,最终,以一种或另一种方式正确答案。事实上,当我最初发布它时,StackOverflow 甚至邀请我通过分享我的知识来回答我自己的问题。事实上,那是我动机的一部分。甚至我所找到的任何地方都没有收集到这件事的事实。

所以:

问:当使用 AndroidHttpClient 通过 HTTPS 发出 REST 请求时,如何指定要使用的 SSL 协议和密码?

这个很重要。很好的观点是,在服务器上可以做很多事情,但也有限制。同一台服务器必须为浏览器(包括旧浏览器)以及其他客户端提供服务。这意味着服务器必须支持广泛的协议和密码。即使在 Android 中,如果您必须支持许多不同的版本,您也必须支持许多不同的协议和密码。

更重要的是,默认情况下,OpenSSL在 SSL 握手期间尊重客户端的密码偏好,而不是服务器的偏好。例如,请参阅这篇文章,其中说您可以通过设置 SSL_OP_CIPHER_SERVER_PREFERENCE 来覆盖客户端中的该行为。是否甚至可以在 Java 中的 SSLSocket 上设置此选项并不完全清楚。即使可以,您也可以自己设置密码列表或告诉您的客户端遵守服务器的列表。否则,您将获得 Android 默认值,无论您正在运行的版本是什么(而不是您链接的版本)。

如果您采用默认值,则可以在此处看到客户端通过 Jellybean 4.2+ 客户端发送到服务器的首选项列表,从第 504 行开始。协议的默认列表从第 620 行开始。虽然 Jellybean 4.2+ 包括对 OpenSSL 的支持1.0.1,尤其是 TLSv1.1 和 TLSv1.2,这些协议默认不启用。如果你不做我做过的事情,你就不能利用 TLSv1.2 支持,尽管最近版本的 Android 上宣传了对 TLSv1.2 的支持。当您回顾以前的 Android 版本时,细节会有所不同。至少,您可能希望仔细查看所有受支持版本的默认设置,并了解您的客户端实际在做什么。你可能会感到惊讶。

关于对各种协议和密码的支持,您可以说更多。关键是,有时可能需要在客户端中更改这些设置。

A. 使用自定义 SSLSocketFactory

这对我来说效果很好,最后,它不是很多代码。但由于以下几个原因,这有点棘手:

  • 有两个不同的 SSLSocketFactory 类。客户端需要一个 org.apache.http.conn.ssl.SSLSocketFactory,但 OpenSSL 返回一个 javax.net.ssl.SSLSocketFactory。这绝对是令人困惑的。我使用委托来拨打另一个电话,没有太大问题。
  • 注意 OpenSSLContextImpl 和 SSLContextImpl 之间的区别。一个只是包裹另一个,但它们不可互换。当我使用 SSLContextImpl.engineGetSocketFactory 方法时——我忘记了到底发生了什么,但有些事情悄悄地失败了。确保使用 OpenSSLContextImpl 来获取您的套接字工厂,而不是 SSLContextImpl。您也许还可以使用 javax.net.ssl.SSLSocketFactory.getDefault(),但我不确定。
  • 您不能轻易继承 AndroidHttpClient,因为它的构造函数是私有的。这是不幸的,因为它提供了一些其他的好东西,比如确保正确关闭它而不是泄漏资源。DefaultHttpClient 工作得很好。我从 AndroidHttpClient 借用了 newInstance 方法(大约第 105 行)。

要点:

public class SecureSocketFactory extends SSLSocketFactory {
    @Override
    public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
        // order should not matter here; the server should select the highest
        // one from this list that it supports
        s.setEnabledProtocols(new String[] { "TLSv1.2", "TLSv1" });

        // order matters here; specify in preference order
        s.setEnabledCipherSuites(new String[] { "ECDHE-RSA-RC4-SHA", "RC4-SHA" });

然后:

// when creating client
HttpParams params;
SchemeRegistry schemeRegistry = new SchemeRegistry();

// use custom socket factory for https
SSLSocketFactory sf = new SecureSocketFactory();
schemeRegistry.register(new Scheme("https", sf, 443));

// use the default for http
schemeRegistry.register(new Scheme("http",
            PlainSocketFactory.getSocketFactory(), 80));

ClientConnectionManager manager =
            new ThreadSafeClientConnManager(params, schemeRegistry);

HttpClient client = new DefaultHttpClient(manager, params);

在 Android 3.0(Honeycomb/SDK 11)以下,支持的密码选择变得更加有限,并且没有动机覆盖默认值。在 FROYO/SDK 8 上,我的 SecureSocketFactory 因某种原因炸毁,陪审团在 10 上出局。但它似乎适用于 11 向上就好了。

完整的解决方案在公共 github repo中。

另一种解决方案可能是使用 HttpsUrlConnection,它可以轻松使用自定义套接字工厂,但我想您可能会失去高级 HTTP 客户端的更多便利。我对 HttpsUrlConnection 没有任何经验。

4

1 回答 1

1

使用 AndroidHttpClient 通过 HTTPS 发出 REST 请求时,如何指定要使用的 SSL 协议和密码?

我不相信你可以做到这一点AndroidHttpClient。我为强化通道所做的一切(如密码列表、证书固定和公钥固定)都需要在某个地方使用自定义类,无论SSLSocketFactoryX509TrustManager. 那是Java,那是Android。请参阅使用 HttpsURLConnection 时如何覆盖 Android 发送到服务器的密码列表?.

于 2013-12-02T01:07:23.490 回答