【问题标题】:Android HTTPS and Cookies, API level 8Android HTTPS 和 Cookie,API 级别 8
【发布时间】:2012-10-30 16:45:07
【问题描述】:

我正在编写一个必须通过 HTTPS 连接到服务器的 Android 应用程序。我尝试的第一个解决方案是这个: (不要介意安全漏洞)

final static HostnameVerifier DO_NOT_VERIFY = new HostnameVerifier() {
    public boolean verify(String hostname, SSLSession session) {
        return true;
    }
};
private static void trustAllHosts() {
    // Create a trust manager that does not validate certificate chains
    TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
        public java.security.cert.X509Certificate[] getAcceptedIssuers() {
            return new java.security.cert.X509Certificate[] {};
        }

        public void checkClientTrusted(X509Certificate[] chain,
                String authType) throws CertificateException {
        }

        public void checkServerTrusted(X509Certificate[] chain,
                String authType) throws CertificateException {
        }
    } };

    // Install the all-trusting trust manager
    try {
        SSLContext sc = SSLContext.getInstance("TLS");
        sc.init(null, trustAllCerts, new java.security.SecureRandom());
             HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
    } catch (Exception e) {
        Log.d("USR_SSL", e.getMessage());
    }
}
//...

@Override
protected String doInBackground(String... params) {
    try {

        URL url = new URL(params[0]);
                    //This is an HTTPS url
        String jsonStr = "";
        if(params.length > 1) {
            jsonStr = params[1];
        }
        HttpsURLConnection urlConn = (HttpsURLConnection) url.openConnection();
        trustAllHosts();
        urlConn.setHostnameVerifier(DO_NOT_VERIFY);
        urlConn.setRequestProperty("Content-Type", "application/json");
        urlConn.setRequestProperty("Accept", "application/json");
        urlConn.setRequestMethod("POST");

        OutputStream os = urlConn.getOutputStream();
        os.write(jsonStr.getBytes());
        os.flush();
//...

一切都很好(几乎),直到我意识到我还必须使用身份验证、会话和所有这些好东西。使用它应该真的很好:

CookieManager cookieManager = new CookieManager();  
CookieHandler.setDefault(cookieManager);

但不幸的是,我们必须支持 Android API level 8,这意味着上面两行代码将不起作用。鉴于此,我在 Internet 上搜索了几个小时,试图使用 Apache 类构建解决方案,这些类似乎同时支持 HTTPS 和 Cookie。

这是我设法缝合在一起的代码:

public class ConnectionMediator {
public class MySSLSocketFactory extends SSLSocketFactory {
    SSLContext sslContext = SSLContext.getInstance("TLS");

    public MySSLSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
        super(truststore);

        TrustManager tm = new X509TrustManager() {
            public void checkClientTrusted(X509Certificate[] chain, String authType) {
            }

            public void checkServerTrusted(X509Certificate[] chain, String authType) {
            }

            public X509Certificate[] getAcceptedIssuers() {
                return null;
            }
        };
        sslContext.init(null, new TrustManager[] { tm }, null);
    }

    @Override
    public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException {
        return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose);
    }

    @Override
    public Socket createSocket() throws IOException {
        return sslContext.getSocketFactory().createSocket();
    }
}
public void tryConnect(String url, String data) {
    try {
        //SSL Stuff
        KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
        trustStore.load(null, null);

        SSLSocketFactory sf = new MySSLSocketFactory(trustStore);
        sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);

        HttpParams params = new BasicHttpParams();
        HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
        HttpProtocolParams.setContentCharset(params, HTTP.UTF_8);

        SchemeRegistry registry = new SchemeRegistry();
        registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
        registry.register(new Scheme("https", sf, 443));

        ClientConnectionManager ccm = new ThreadSafeClientConnManager(params, registry);

        DefaultHttpClient httpClient = new  DefaultHttpClient(ccm, params);

        //Cookie stuff
        HttpContext localContext = new BasicHttpContext();
        HttpResponse response = null;
        HttpPost httpPost = null;
        StringEntity tmp = null;

        httpClient.getParams().setParameter(ClientPNames.COOKIE_POLICY, CookiePolicy.RFC_2109);
        httpPost = new HttpPost(url);

        tmp = new StringEntity(data,"UTF-8");
        httpPost.setEntity(tmp);

        response = httpClient.execute(httpPost,localContext);
    } catch(Exception e) {
        Log.d("USR_DEBUG", e.getClass().toString() + ": " + e.getMessage());
    }

}
}

在撰写本文时,我收到了NetworkOnMainThreadException,但这并不重要;重要的是我想指出的是,我不知道自己在做什么,例如,要通过 HTTPS 简单地连接并使用 cookie,必须使用 13 种不同的我从未听说过的课程。显然,我对 HTTPS/Java 网络类的了解几乎为零,但尽管如此,我还是希望得到更直观的东西。因此,我的问题不是“这不起作用”类型的问题,而是“我应该做什么”,甚至是“我如何学习我必须做的事情?”。

非常感谢,

一个非常困惑的程序员

【问题讨论】:

  • 您正在做的很大一部分只是因为您的 https 证书无法识别(很可能是自签名?)
  • 确实如此。在我们的服务器决定做一个适当的证书之前,我会坚持这一点。

标签: java android session cookies https


【解决方案1】:

我最初的问题有两个部分:第一,如何使用 HTTPS,第二,如何同时使用 cookie。

我的问题不够彻底,因为我已经部分回答了 - 我最初发布的代码适用于 HTTPS,而出现 NetworkOnMainThreadException 是因为我没有在单独的线程中运行代码,例如,使用AsyncTask

但是,为了也能正确使用 cookie,应该使用类似于以下的解决方案:

public class State {
    private static HttpContext httpContext;
    public static HttpContext getHttpContext() {
        if(httpContext == null) {
            httpContext = new BasicHttpContext();
            CookieStore cookieStore = getCookieStore();
            httpContext.setAttribute(ClientContext.COOKIE_STORE, cookieStore);
        }
        return httpContext;
    }
    private static CookieStore cookieStore;
    public static CookieStore getCookieStore() {
        if(cookieStore == null) {
            cookieStore = new BasicCookieStore();
        }
        return cookieStore;
    }
}

我不确定这是否是“Android”的做法(使用静态类),但它有效:

    //...
    //Connection objects
    DefaultHttpClient httpClient = new DefaultHttpClient(ccm, params);
    HttpPost httpPost = null;

    //Cookie stuff
    HttpContext httpContext = State.getHttpContext();
    httpClient.getParams().setParameter(ClientPNames.COOKIE_POLICY, CookiePolicy.RFC_2109);
    //....

如果使用相同的CookieStoreHttpContext,似乎每次都使用另一个HttpClient 会保留会话。 虽然这个答案使“事情正常”(这是我现在需要的),但它并不是一个彻底的答案,因为它根本没有解释为什么需要 10 多个类来将所有东西连接在一起。

【讨论】:

  • 关于那些你需要将所有东西连接在一起的 10/13 类,这就是 Apache HttpClient 的运行方式:它的学习曲线有点陡峭,尤其是如果你不了解 HTTP 的具体细节/HTTPS 规范。你可能会称它为过度设计(例如NameValuePair,真的吗?)或者惊叹于它如何为 HTTP 中的每一个概念提供一个类。无论哪种方式,您都可能无法减少该数量,这是使用“高度灵活和功能强大”的库所付出的代价,正如 HttpClient 所描述的那样。
  • 这不仅仅是过度设计。这是一个地狱般的图书馆。每次输入字符时,使用它就像被巨大的仙人掌击中一样。当您需要使用 8 个不同的类来获取页面内容和状态时,您知道有些东西是非常过度设计的。
猜你喜欢
  • 1970-01-01
  • 2013-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-04
  • 2011-03-07
  • 2012-04-28
  • 2011-04-14
  • 1970-01-01
相关资源
最近更新 更多