問題:
項目中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種方法都解決了作者遇到的問題,這裏記錄下。以防下次遇到,希望能給遇到相同問題朋友有所參考幫助。