摘自: http://www.kankanews.com/ICkengine/archives/9634.shtmlhtml
項目中Android https或http請求地址重定向爲HTTPS的地址,相信不少人都遇到了這個異常(無終端認證):
javax.net.ssl.SSLPeerUnverifiedException: No peer certificatejava
log裏出現這個異常,做者第一次遇到,不知道啥意思。看下字面意思,是ssl協議中沒有終端認證。SSL?做者沒用到ssl協議呀,只是經過httpClient請求一個重定向https的地址。
好吧,google下,知道了個差很少狀況的帖子,http://www.eoeandroid.com/thread-161747-1-1.html。恩恩,一個不錯的帖子,給出了個解決方案。照着來試下。添加個繼承SSLSocketFactory的
自定義類。並在初始化httpclient支持https時,註冊進去。看下面代碼:android
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的異常了。服務端的數據正常返回了。,狂喜中…算法
狂喜中,得分析這問題誒。否則老大來問,啥狀況?楞半天不知道咋說(做者就常常這樣,因此吸收教訓。因此的弄懂出現的問題,學習+彙報工做)。
思來想去,就是做者請求的是一個重定向https的地址。好吧,那就學習下https(以前被老大深深的教過,http就是request/response)。繼續搜索吧,少年。下面總結下學習到的https知識。瀏覽器
HTTPS:超文本安全傳輸協議,和HTTP相比,多了一個SSL/TSL的認證過程,端口爲443。(鄙視下以前說的)安全
做者沒用到ssl協議呀,只是經過httpClient請求一個重定向https的地址app
1.peer終端發送一個request,https服務端把支持的加密算法等以證書的形式返回一個身份信息(包含ca頒發機構和加密公鑰等)。eclipse
2.獲取證書以後,驗證證書合法性。socket
3.隨機產生一個密鑰,並以證書當中的公鑰加密。ide
4.request https服務端,把用公鑰加密過的密鑰傳送給https服務端。
5.https服務端用本身的密鑰解密,獲取隨機值。
6.以後雙方傳送數據都用此密鑰加密後通訊。
看下面一張網上的得來的https的時序圖:
好吧,大概的流程知道了。定位已經很是清楚了。在第2步驗證證書時,沒法驗證。爲啥沒法驗證呢?沒有添加信任。詳細參考下
http://www.cnblogs.com/P_Chou/archive/2010/12/27/https-ssl-certification.html講的很是清楚https-ssl的認證過程,膜拜下該做者
這樣想來,上面提供的解決方案就是添加默認信任所有證書。以此來經過接下來的通訊。
可是,這樣問題是解決了。可是以爲仍是不帶靠譜(信任所有證書有點危險)。繼續噼噼啪啪的網上搜索一番。又找到了一種解決方案,其過程大體這樣的:
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種方法都解決了做者遇到的問題,這裏記錄下。以防下次遇到,但願能給遇到相同問題朋友有所參考幫助。