android httpClient 支持HTTPS的2種處理方式

android httpClient 支持HTTPS的2種處理方式

問題:

項目中Android https或http請求地址重定向爲HTTPS的地址,相信很多人都遇到了這個異常(無終端認證):
 javax.net.ssl.SSLPeerUnverifiedException: No peer certificate


解決過程:

1.沒遇到過的問題,搜索吧,少年

log裏出現這個異常,作者第一次遇到,不知道啥意思。看下字面意思,是ssl協議中沒有終端認證。SSL?作者沒用到ssl協議呀,只是通過httpClient請求一個重定向https的地址。
好吧,google下,知道了個差不多情況的帖子,http://www.eoeandroid.com/thread-161747-1-1.html。恩恩,一個不錯的帖子,給出了個解決方案。照着來試下。添加個繼承SSLSocketFactory的
自定義類。並在初始化httpclient支持https時,註冊進去。看下面代碼:

public class HttpClientHelper {

	private static HttpClient httpClient;

	private HttpClientHelper() {
	}

	public static synchronized HttpClient getHttpClient() {

		if (null == httpClient) {
			// 初始化工作
			try {
				KeyStore trustStore = KeyStore.getInstance(KeyStore
						.getDefaultType());
				trustStore.load(null, null);
				SSLSocketFactory sf = new SSLSocketFactoryEx(trustStore);
				sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);  //允許所有主機的驗證

				HttpParams params = new BasicHttpParams();

				HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
				HttpProtocolParams.setContentCharset(params,
						HTTP.DEFAULT_CONTENT_CHARSET);
				HttpProtocolParams.setUseExpectContinue(params, true);

				// 設置連接管理器的超時
				ConnManagerParams.setTimeout(params, 10000);
				// 設置連接超時
				HttpConnectionParams.setConnectionTimeout(params, 10000);
				// 設置socket超時
				HttpConnectionParams.setSoTimeout(params, 10000);

				// 設置http https支持
				SchemeRegistry schReg = new SchemeRegistry();
				schReg.register(new Scheme("http", PlainSocketFactory
						.getSocketFactory(), 80));
				schReg.register(new Scheme("https", sf, 443));

				ClientConnectionManager conManager = new ThreadSafeClientConnManager(
						params, schReg);

				httpClient = new DefaultHttpClient(conManager, params);
			} catch (Exception e) {
				e.printStackTrace();
				return new DefaultHttpClient();
			}
		}
		return httpClient;
	}

}

class SSLSocketFactoryEx extends SSLSocketFactory {

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

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

		TrustManager tm = new X509TrustManager() {

			@Override
			public java.security.cert.X509Certificate[] getAcceptedIssuers() {
				return null;
			}

			@Override
			public void checkClientTrusted(
					java.security.cert.X509Certificate[] chain, String authType)
					throws java.security.cert.CertificateException {

			}

			@Override
			public void checkServerTrusted(
					java.security.cert.X509Certificate[] chain, String authType)
					throws java.security.cert.CertificateException {

			}
		};

		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();
	}
}

ok,run下,狂亂的點到測試按鈕,深吸口氣,盯着eclipse中的logat。咦?神奇的竟然沒有報之前的 javax.net.ssl.SSLPeerUnverifiedException: No peer certificate的異常了。服務端的數據正常返回了。,狂喜中...


2.瞭解並分析問題

狂喜中,得分析這問題誒。不然老大來問,啥情況?楞半天不知道咋說(作者就經常這樣,所以吸取教訓。所以的弄懂出現的問題,學習+彙報工作)。
思來想去,就是作者請求的是一個重定向https的地址。好吧,那就學習下https(之前被老大深深的教過,http就是request/response)。繼續搜索吧,少年。下面總結下學習到的https知識。

2.1 https


HTTPS:超文本安全傳輸協議,和HTTP相比,多了一個SSL/TSL的認證過程,端口爲443。(鄙視下之前說的)

作者沒用到ssl協議呀,只是通過httpClient請求一個重定向https的地址


1.peer終端發送一個request,https服務端把支持的加密算法等以證書的形式返回一個身份信息(包含ca頒發機構和加密公鑰等)。

2.獲取證書之後,驗證證書合法性。

3.隨機產生一個密鑰,並以證書當中的公鑰加密。

4.request https服務端,把用公鑰加密過的密鑰傳送給https服務端。

5.https服務端用自己的密鑰解密,獲取隨機值。

6.之後雙方傳送數據都用此密鑰加密後通信。

看下面一張網上的得來的https的時序圖:



2.2分析下出現問題的原因


好吧,大概的流程知道了。定位已經非常清楚了。在第2步驗證證書時,無法驗證。爲啥無法驗證呢?沒有添加信任。詳細參考下

http://www.cnblogs.com/P_Chou/archive/2010/12/27/https-ssl-certification.html講的非常清楚https-ssl的認證過程,膜拜下該作者


這樣想來,上面提供的解決方案就是添加默認信任全部證書。以此來通過接下來的通信。

3.解決問題

但是,這樣問題是解決了。但是覺得還是不帶靠譜(信任全部證書有點危險)。繼續噼噼啪啪的網上搜索一番。又找到了一種解決方案,其過程大致這樣的:

1.瀏覽器訪問https地址,保存提示的證書到本地,放到android項目中的assets目錄。

2.導入證書,代碼如下。

3.把證書添加爲信任。


String requestHTTPSPage(String mUrl) {
		InputStream ins = null;
		String result = "";
		try {
			ins = context.getAssets().open("app_pay.cer"); //下載的證書放到項目中的assets目錄中
			CertificateFactory cerFactory = CertificateFactory
					.getInstance("X.509");
			Certificate cer = cerFactory.generateCertificate(ins);
			KeyStore keyStore = KeyStore.getInstance("PKCS12", "BC");
			keyStore.load(null, null);
			keyStore.setCertificateEntry("trust", cer);

			SSLSocketFactory socketFactory = new SSLSocketFactory(keyStore);
			Scheme sch = new Scheme("https", socketFactory, 443);
			HttpClient mHttpClient = new DefaultHttpClient();
			mHttpClient.getConnectionManager().getSchemeRegistry()
					.register(sch);

			BufferedReader reader = null;
			try {
				Log.d(TAG, "executeGet is in,murl:" + mUrl);
				HttpGet request = new HttpGet();
				request.setURI(new URI(mUrl));
				HttpResponse response = mHttpClient.execute(request);
				if (response.getStatusLine().getStatusCode() != 200) {
					request.abort();
					return result;
				}

				reader = new BufferedReader(new InputStreamReader(response
						.getEntity().getContent()));
				StringBuffer buffer = new StringBuffer();
				String line = null;
				while ((line = reader.readLine()) != null) {
					buffer.append(line);
				}
				result = buffer.toString();
				Log.d(TAG, "mUrl=" + mUrl + "\nresult = " + result);
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				if (reader != null) {
					reader.close();
				}
			}
		} catch (Exception e) {
			// TODO: handle exception
		} finally {
			try {
				if (ins != null)
					ins.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return result;
	}


接着,驗證下唄。吼吼,稀裏糊塗的又可以了。感動的淚流滿面。


最後總結:

2種方法都解決了作者遇到的問題,這裏記錄下。以防下次遇到,希望能給遇到相同問題朋友有所參考幫助。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章