最近遇到網絡安全方面的問題,要將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();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
調用示例:
httpPostData(MainActivity.this, url, content);
- 1
- 1
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());
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
再在所有https開始進行請求之前,執行一次即可:
HTTPSTrustManager.allowAllSSL();//信任所有證書
- 1
- 1
後面就是正常的進行https訪問就可以了:
httpPostData(MainActivity.this, url, content);
- 1
- 1
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;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
然後,在使用 HttpsURLConnection 的過程中,也就是httpsPostData()函數中,使用指定證書的 SSLContext 即可:
conn.setSSLSocketFactory(getSSLContext(context).getSocketFactory());
- 1
- 1
當然,如果仔細看了前面的 httpsPostData()函數內容的話,就知道前面的代碼中已經有這句話了,只是被註釋掉了。打開就可以了,就是使用指定證書的了。
至於調用POST請求的地方,是一樣的:
httpPostData(MainActivity.this, url, content);
- 1
- 1
最後總結一下:
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