Android:使用HttpsURLConnection建立HTTPS(SSL)连接

16

我有两个应用程序,一个是Servlet/Tomcat服务器,另一个是Android应用程序。

我想使用HttpURLConnection在它们之间发送和接收XML。

代码:

    private String sendPostRequest(String requeststring) {

    DataInputStream dis = null;
    StringBuffer messagebuffer = new StringBuffer();

    HttpURLConnection urlConnection = null;

    try {
        URL url = new URL(this.getServerURL());

        urlConnection = (HttpURLConnection) url.openConnection();           

        urlConnection.setDoOutput(true);

        urlConnection.setRequestMethod("POST");

        OutputStream out = new BufferedOutputStream(urlConnection.getOutputStream());

        out.write(requeststring.getBytes());

        out.flush();

        InputStream in = new BufferedInputStream(urlConnection.getInputStream());

        dis = new DataInputStream(in);

        int ch;

        long len = urlConnection.getContentLength();

        if (len != -1) {

            for (int i = 0; i < len; i++)

                if ((ch = dis.read()) != -1) {

                    messagebuffer.append((char) ch);
                }
        } else {

            while ((ch = dis.read()) != -1)
                messagebuffer.append((char) ch);
        }

        dis.close();

    } catch (Exception e) {

        e.printStackTrace();

    } finally {

        urlConnection.disconnect();
    }

    return messagebuffer.toString();
}

现在,我需要使用SSL来发送XML以保证安全性。

首先,我使用Java Keytool生成.keystore文件。

Keytool  -keygen -alias tomcat -keyalg RSA

然后我将XML代码放在Tomcat的server.xml文件中以使用SSL

<Connector 
port="8443" protocol="HTTP/1.1" SSLEnabled="true"
maxThreads="150" scheme="https" secure="true"
keystoreFile="c:/Documents and Settings/MyUser/.keystore"
keystorePass="password"
clientAuth="false" sslProtocol="TLS" 
/>

然后,我将它从HttpURLConnection更改为HttpsURLConnection

    private String sendPostRequest(String requeststring) {

    DataInputStream dis = null;
    StringBuffer messagebuffer = new StringBuffer();

    HttpURLConnection urlConnection = null;

    //Conexion por HTTPS
    HttpsURLConnection urlHttpsConnection = null;

    try {
        URL url = new URL(this.getServerURL());

        //urlConnection = (HttpURLConnection) url.openConnection();         

        //Si necesito usar HTTPS
        if (url.getProtocol().toLowerCase().equals("https")) {

            trustAllHosts();

            //Creo la Conexion
            urlHttpsConnection = (HttpsURLConnection) url.openConnection();

            //Seteo la verificacion para que NO verifique nada!!
            urlHttpsConnection.setHostnameVerifier(DO_NOT_VERIFY);

            //Asigno a la otra variable para usar simpre la mism
            urlConnection = urlHttpsConnection;

        } else {

            urlConnection = (HttpURLConnection) url.openConnection();
        }

//Do the same like up

并添加一个trustAllHosts方法来信任每个服务器(不检查任何证书)

private static void trustAllHosts() {

    X509TrustManager easyTrustManager = new X509TrustManager() {

        public void checkClientTrusted(
                X509Certificate[] chain,
                String authType) throws CertificateException {
            // Oh, I am easy!
        }

        public void checkServerTrusted(
                X509Certificate[] chain,
                String authType) throws CertificateException {
            // Oh, I am easy!
        }

        public X509Certificate[] getAcceptedIssuers() {
            return null;
        }

    };

    // Create a trust manager that does not validate certificate chains
    TrustManager[] trustAllCerts = new TrustManager[] {easyTrustManager};

    // 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) {
            e.printStackTrace();
    }
}
那些更改非常好用,但我不想信任每个服务器。我想使用我的密钥库文件来验证连接并正确使用SSL。我在互联网上阅读了很多内容并进行了很多测试,但我不明白我需要做什么以及如何做。
有人能帮帮我吗?
非常感谢!
对不起,我的英语很差。
-------------------------更新2011/08/24-------------------------------------------------
好的,我还在努力解决这个问题。我创建了一个新方法来设置KeyStore、InputStream等。
该方法看起来像这样:
private static void trustIFNetServer() {

    try {
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());

        KeyStore ks = KeyStore.getInstance("BKS");

        InputStream in = context.getResources().openRawResource(R.raw.mykeystore);

        String keyPassword = "password"; 

        ks.load(in, keyPassword.toCharArray());

        in.close();

        tmf.init(ks);

        TrustManager[] tms = tmf.getTrustManagers();    

        SSLContext sc = SSLContext.getInstance("TLS");

    sc.init(null, tms, new java.security.SecureRandom());

    } catch (Exception e) {
    e.printStackTrace();
    }
}

起初我在密钥和证书方面遇到了很多问题,但现在似乎已经解决了(至少我认为是这样的)。

目前我的问题是超时异常。我不知道它为什么会出现。我认为可能与数据写入有关,但我还没有找到解决方法。

有什么想法吗?


你是使用由知名证书机构签发的证书,还是使用自签名证书? - President James K. Polk
3个回答

7
您需要创建一个信任库文件,用于您自签名证书的使用,详见 此处。 在客户端使用它与您的服务器建立连接。无论使用JKS或其他格式都可以,我现在假设使用JKS。
为了实现您的想法,您需要不同的TrustManager。您可以使用TrustManagerFactory并将其信任设置与您新创建的信任库一起使用。
TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX");
KeyStore ks = KeyStore.getInstance("JKS");
FileInputStream in = new FileInputStream("<path to your key store>");
ks.load(in, "password".toCharArray());
in.close();
tmf.init(ks);
TrustManager[] tms = tmf.getTrustManagers();

使用tms来初始化你的SSLContext,以便于新的信任设置能够用于你的SSL/TLS连接。
此外,你应该确保服务器TLS证书中的CN部分与你的服务器完全合格域名(FQDN)相等。例如,如果你的服务器基本URL是'https://www.example.com',那么证书的CN应该是'www.example.com'。这是为了进行主机名验证,这个功能可以防止中间人攻击。你可以禁用这个功能,但只有使用它才能使你的连接真正安全。

我认为他需要正确配置Android客户端的信任存储,而不是服务器的。 - President James K. Polk
谢谢您的帮助。我会尽力理解这些并进行一些测试。非常感谢! - Mark Comix
我有一个问题,当你说:FileInputStream in = new FileInputStream("<path to your key store>");我需要使用哪个文件?我有"keystore"(没有扩展名),"truststore"(没有扩展名)和"test.cer"。 - Mark Comix
我还有一些问题,但这是正确的路径。非常感谢。 - Mark Comix
嗯,我提交了一条评论,但似乎没有出现...抱歉。是的,在那种情况下,您需要使用信任存储文件。 - emboss
有没有人有包括 @emboss 这段代码的 Gist?这将非常不错,特别是因为关于在 Android 上信任自签名证书的明确示例并不多 :)。 - ivanleoncz

0
创建您的信任存储,将其存储为资产并使用它来初始化此SocketFactory。然后使用该工厂代替您自己的“信任所有人”的工厂。

谢谢,你有一些例子吗? - Mark Comix
不是一个完整的示例,但是一旦您加载了信任存储(其中包含服务器证书),只需将其与密码一起传递给构造函数即可。然后,您可以将生成的 SocketFactory 实例传递给 HttpsURLConnection.setDefaultSSLSocketFactory() - Nikolay Elenkov

0

没有证书和握手,传输到我的服务器是否安全? - Geek Guy
@GeekGuy 不要这么做。 - Rainmaker

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