使用HttpsURLConnection的3种方法小结

最近遇到网络安全方面的问题,要将http转移到https,由于在工程中使用了HttpURLConnection,所以要相应的转而使用HttpsURLConnection,当然大部分是参考的网络上一些前辈们的成果,过程中也遇到了一些坑,在这里进行一下总结。

由于https涉及到证书的认证方式,这里简单介绍一下:
关于证书,可以简单把它理解为网站的身份证。而给网站颁发身份证的就是CA(证书颁发机构)。
可以颁发证书的CA有很多(国内外都有),只有少数CA被认为是权威、公正的,这些CA颁发的证书,浏览器、操作系统才认为是信得过的。
在Android系统中,就有一个根证书信任列表,若我们的证书是由这个列表中的某个根证书的子证书,就不需要在https使用过程中特别指定了。
我们自己也可以自己制作证书,例如使用OpenSSL就可以生成一个CA根证书,然后用这个根证书颁发两个子证书server和client,server证书放在服务器端,而这个client证书就可以用于浏览器或者安卓app中。这种自己制作的证书,就必须在app中指定了,否则https握手是不能成功的。
我就按使用证书的不同方式来进行分别说明:

1,信任系统提供的证书(权威CA颁发);
2,全部信任证书;
3,信任指定证书;

1,信任系统提供的证书

这是最简单的方式,相比较于http访问,转到https协议,只是将HttpURLConnection 替换为 HttpsURLConnection 就够了。
下面是一个使用 HttpsURLConnection 实现的POST请求:

public static void httpsPostData(final Context context, final String urlPath, final String content){
        new Thread()
        {
            @Override
            public void run() {
                // TODO Auto-generated method stub
                Looper.prepare();
                URL url;
                try {
                    url = new URL(TimeValidity.addTimeValidityUrl(urlPath));
                    HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
                    //conn.setSSLSocketFactory(getSSLContext(context).getSocketFactory());
                    conn.setConnectTimeout(TIMEOUT_LONG);//5
                    conn.setReadTimeout(TIMEOUT_LONG);
                    conn.setDoOutput(true);// 设置允许输出
                    conn.setRequestMethod("POST");
                    conn.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)");
                    conn.setRequestProperty("Charset", "UTF-8");
                    conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
                    OutputStream os = conn.getOutputStream();
                    os.write(content.getBytes());
                    os.close();

                    /* 服务器返回的响应码 */
                    int code = conn.getResponseCode();
                    Log.i("https","code="+code);
                    if (code == 200) {
                        BufferedReader in = new BufferedReader(
                                new InputStreamReader(conn.getInputStream(), "UTF-8"));
                        String retData = null;
                        String responseData = "";
                        while ((retData = in.readLine()) != null) {
                            responseData += retData;
                        }
                        in.close();                     
                    } else {
                        Log.i("https","return error");
                    }
                } catch (MalformedURLException e) {
                    e.printStackTrace();                    
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (Exception e) {
                    e.printStackTrace();                
                }
                Looper.loop();
            }
        }.start();
    }

调用示例:

httpPostData(MainActivity.this, url, content);

2,全部信任证书

添加HTTPSTrustManager类,如下:

public class HTTPSTrustManager implements X509TrustManager {

    private static TrustManager[] trustManagers;
    private static final X509Certificate[] _AcceptedIssuers = new X509Certificate[] {};

    @Override
    public void checkClientTrusted(
            java.security.cert.X509Certificate[] x509Certificates, String s)
            throws java.security.cert.CertificateException {
        // To change body of implemented methods use File | Settings | File
        // Templates.
    }

    @Override
    public void checkServerTrusted(
            java.security.cert.X509Certificate[] x509Certificates, String s)
            throws java.security.cert.CertificateException {
        // To change body of implemented methods use File | Settings | File
        // Templates.
    }

    @Override
    public X509Certificate[] getAcceptedIssuers() {
        return _AcceptedIssuers;
    }

    public static void allowAllSSL() {
        HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {

            @Override
            public boolean verify(String arg0, SSLSession arg1) {
                // TODO Auto-generated method stub
                return true;
            }

        });

        SSLContext context = null;
        if (trustManagers == null) {
            trustManagers = new TrustManager[] { new HTTPSTrustManager() };
        }

        try {
            context = SSLContext.getInstance("TLS");
            context.init(null, trustManagers, new SecureRandom());
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (KeyManagementException e) {
            e.printStackTrace();
        }
        HttpsURLConnection.setDefaultSSLSocketFactory(context.getSocketFactory());
    }
}

再在所有https开始进行请求之前,执行一次即可:

HTTPSTrustManager.allowAllSSL();//信任所有证书

后面就是正常的进行https访问就可以了:

httpPostData(MainActivity.this, url, content);

3,信任指定证书

先要获取到证书,我们可以放到assert目录下,例如这里使用的证书的文件名为“root.crt”。
通过如下函数来读取,并返回SSLContext:

    public static SSLContext getSSLContext(Context inputContext){
        SSLContext context = null;
        try {
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            InputStream in = inputContext.getAssets().open("root.crt");
            Certificate ca = cf.generateCertificate(in);
            KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
            keystore.load(null, null);
            keystore.setCertificateEntry("ca", ca);
            String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
            TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
            tmf.init(keystore);
            // Create an SSLContext that uses our TrustManager
            context = SSLContext.getInstance("TLS");
            context.init(null, tmf.getTrustManagers(), null);
        } catch (Exception e){
            e.printStackTrace();
        }
        return context;
    }

然后,在使用 HttpsURLConnection 的过程中,也就是httpsPostData()函数中,使用指定证书的 SSLContext 即可:

conn.setSSLSocketFactory(getSSLContext(context).getSocketFactory());

当然,如果仔细看了前面的 httpsPostData()函数内容的话,就知道前面的代码中已经有这句话了,只是被注释掉了。打开就可以了,就是使用指定证书的了。

至于调用POST请求的地方,是一样的:

httpPostData(MainActivity.this, url, content);

最后总结一下:

1,全部信任证书:不太安全,Google也不推荐。但是毕竟是https,比http安全多了,只是还存在被中间人攻击的风险;

2,信任指定证书:这种方式保证了网络传输链路的安全,是可以防住中间人攻击的。
但是问题可能会出在App上:这个证书直接放在app中,若是由于某些原因导致证书需要更新,就需要更新所有的app,在用户量较大的情况下,这几乎是不可能完成的任务。

3,信任系统提供的证书(CA颁发);这种方式最好,既安全又好维护,更换一个CA颁发的证书,对代码不需要做任何改动,但是可能需要花钱。也可以找些免费的证书,但是使用期限可能有较大的限制。

这三种方式各有特色,具体采用哪种方式,还是需要根据自己项目的实际情况来确定。

参考:

http://blog.csdn.net/whu_zhangmin/article/details/45868057
http://www.cnblogs.com/cxjchen/p/3152832.html

猜你喜欢

转载自blog.csdn.net/lintax/article/details/70195138