android javax.net.ssl.SSLPeerUnverifiedException: No peer certificate

上善若水發表於2016-08-19

在android開發中,我們時不時會遇到一些非常奇怪的問題。最近,在專案中遇到了SSL證照相關的問題。

最近專案在做伺服器的環境搬遷工作,在新的環境下,移動端的APP請求不到伺服器,完成不了交易。

檢查了下服務端,沒有任何日誌的輸出。因此,判斷是移動端出現了問題,移動端根本將網路請求就沒有傳送到服務端。

在客戶端也沒有任何error的日誌。只是報了這個警告warn

報警告:android javax.net.ssl.SSLPeerUnverifiedException: No peer certificate
大概意思是:SSL檢查未驗證異常:沒有對方的證照


我們先簡單瞭解一下SSL證照的相關知識:

SSL是SecuritySocketLayer的縮寫,技術上稱為安全套接字,可以簡稱為加密通訊協議。
當選擇“SSL安全登入”後登入網站,使用者名稱和密碼會首先加密,然後通過SSL連線在 Internet 上傳送,
沒有人能夠讀取或訪問到您利用該連線傳送的資料。使用SSL可以對通訊(包括E-mail)內容進行高強度的加密,
從而可以有效防止黑客盜取您的使用者名稱、密碼和通訊內容,保證了郵箱的安全性。
為了更安全的保證個人隱私,建議如果沒有特殊情況,儘量選擇該項登入。
一般說來,在網上進行交易時,需要使用數字簽名來表明自己的身份,並使用數字簽名來進行有關的交易操作。
隨著電子商務的盛行,數位簽章的頒發機構 CA (如GlobalSign)中心將為電子商務的發展提供可靠的安全保障。
網站使用數字簽名之後,即可實現 SSL安全登入。


免費SSL證照不能登入:  免費SSL證照是部署在伺服器上面的,給網站的伺服器端和客戶端之間的資料傳輸加密的。 
如果要用證照進行登入,需向CA機構申請客戶端證照才能實現強身份認證功能。


在URL前加https://字首表明是用SSL加密的。 你的電腦與伺服器之間收發的資訊傳輸將更加安全。
Web伺服器啟用SSL需要獲得一個伺服器證照並將該證照與要使用SSL的伺服器繫結。
http和https使用的是完全不同的連線方式,用的埠也不一樣,前者是80,後者是443。http的連線很簡單,是無狀態的,... 
HTTPS協議是由SSL+HTTP協議構建的可進行加密傳輸、身份認證的網路協議
要比http協議安全


參考文章:android平臺實現SSL單雙向驗證


主要程式碼實現:

SSLSocketFactoryEx.java

import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import org.apache.http.conn.ssl.SSLSocketFactory;
public class SSLSocketFactoryEx extends SSLSocketFactory {      
      
    SSLContext sslContext = SSLContext.getInstance("TLS");      
      
    public SSLSocketFactoryEx(KeyStore truststore)      
            throws NoSuchAlgorithmException, KeyManagementException,      
            KeyStoreException, UnrecoverableKeyException {      
        super(truststore);      
      
        TrustManager tm = new X509TrustManager() {      
      
            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();      
    }      
}  
編寫新的HttpClient  getNewHttpClient來代替原有DefaultHttpClient
MyHttpClient.java

import java.security.KeyStore;

import org.apache.http.HttpVersion;
import org.apache.http.client.HttpClient;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.protocol.HTTP;

public class MyHttpClient {
	public static HttpClient getNewHttpClient() {
		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.UTF_8);

			SchemeRegistry registry = new SchemeRegistry();
			registry.register(new Scheme("http", PlainSocketFactory
					.getSocketFactory(), 80));
			registry.register(new Scheme("https", sf, 443));

			ClientConnectionManager ccm = new ThreadSafeClientConnManager(
					params, registry);

			return new DefaultHttpClient(ccm, params);
		} catch (Exception e) {
			return new DefaultHttpClient();
		}
	}
}
接著在專案裡把所有獲得HttpClient例項通過DefaultHttpClient,都改成從 getNewHttpClient獲取 
//HttpClient httpclient = new DefaultHttpClient();
HttpClient httpclient = MyHttpClient.getNewHttpClient();
最後千萬別忘了檢查清單檔案中有沒有這幾個許可權:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.WRITE_APN_SETTINGS"/> 


相關文章