解決https需要驗證問題

這兩天調試鏈接問題有兩個方面:

HttpsURLConnection 中connect 拋出的異常時IOException

採用下列文章描述的

HttpClient httpClient = new DefaultHttpClient();

返回SSLException異常

javax.net.ssl.SSLException: hostname in certificate didn't match: <58.83.158.101> != <*.360buy.com>


兩種方法都正確,只是獲取的異常信息後者更爲詳細,以下是調用堆棧


Exception in thread "main" javax.net.ssl.SSLException: hostname in certificate didn't match: <eps.dev.surepush.cn> != &lt;*.xxxxxxx(公司名稱).com>
at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:220)
at org.apache.http.conn.ssl.BrowserCompatHostnameVerifier.verify(BrowserCompatHostnameVerifier.java:54)
at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:149)
at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:130)
at org.apache.http.conn.ssl.SSLSocketFactory.createSocket(SSLSocketFactory.java:399)
at org.apache.http.impl.conn.DefaultClientConnectionOperator.updateSecureConnection(DefaultClientConnectionOperator.java:203)
at org.apache.http.impl.conn.AbstractPoolEntry.layerProtocol(AbstractPoolEntry.java:277)
at org.apache.http.impl.conn.AbstractPooledConnAdapter.layerProtocol(AbstractPooledConnAdapter.java:138)
at org.apache.http.impl.client.DefaultRequestDirector.establishRoute(DefaultRequestDirector.java:704)
at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:421)
at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:641)
at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:576)
at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:554)
at com.client.ctl.ZTest.main(ZTest.java:195)


具體SSLSocketFactory實現如下,否則不返回SSLException,最終根據調用堆棧,增加this.getHostnameVerifier().verify(host, sslSocket);

class SSLSocketFactoryEx extends SSLSocketFactory {

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

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

            //
            KeyManager[] km = resetKm();
            TrustManager[] tm = resetTm();
            
            sslContext.init(km, tm, null);
        }

        @Override
        public Socket createSocket(Socket socket, String host, int port,
                boolean autoClose) throws IOException, UnknownHostException {
            System.out.println("=== SSLSocketFactoryEx.createSocket === host : " + host);
            SSLSocket sslSocket = (SSLSocket)sslContext.getSocketFactory().createSocket(socket, host,
                    port, autoClose);
            // 這裏沒有驗證,增加後就能正常判讀
            this.getHostnameVerifier().verify(host, sslSocket);
            
            return sslSocket;
        }

        @Override
        public Socket createSocket() throws IOException {
            System.out.println("=== SSLSocketFactoryEx.createSocket ===");
            return sslContext.getSocketFactory().createSocket();
        }
    }


參考:http://my.oschina.net/sourcecoding/blog/80698

今天在開發京東API接口的時候遇到了一個問題。

問題描述:開發京東用戶授權,採用https的訪問協議獲取新的AccessToken時,總是出現異常:

javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

之後在網上找了很多例子,最後摘了一個不需要證書的例子:


之後在運行報另一個異常,執行HttpResponse response = httpClient.execute(httpPost); 報的錯

javax.net.ssl.SSLException: hostname in certificate didn't match: <58.83.158.101> != <*.360buy.com>

原因我猜測可能是在驗證HOST的時候出現了問題,但我始終傳給程序的是域名,爲啥就非要解析成ip呢。。。我鬱悶。我就一直在想怎麼能報HOST變成域名的形式,我debug了httpPost裏的HOST沒有問題還是域名的,我就想那肯定是httpClient中有問題。最後發現httpClient與socketFactory有關係,而socketFactory就像是一個配置信息的存儲,我在這裏找到了一個setHostnameVerifier的方法,貌似是驗證hostName的,需要參數hostnameVerifier,我像之前那個X509TrustManager一樣new了一個X509HostnameVerifier付給setHostnameVerifier方法,然後重寫裏面的方法,發現了驗證的方法verify(String arg0, SSLSession arg1),跳過直接返回true,果然問題解決了。

以下爲正確代碼:

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
54
55
56
57
58
59
60
61
62
63
64
65
/**
     * 發送HTTPS  POST請求
     *
     * @param 要訪問的HTTPS地址,POST訪問的參數Map對象
     * <a href="http://my.oschina.net/u/556800" class="referer" target="_blank">@return</a>  返回響應值
     * */
    public static final String sendHttpsRequestByPost(String url, Map<String, String> params) {
        String responseContent = null;
        HttpClient httpClient = new DefaultHttpClient();
        //創建TrustManager
        X509TrustManager xtm = new X509TrustManager() {
            public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {}
            public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {}
            public X509Certificate[] getAcceptedIssuers() {
                return null;
            }
        };
        //這個好像是HOST驗證
        X509HostnameVerifier hostnameVerifier = new X509HostnameVerifier() {
            public boolean verify(String arg0, SSLSession arg1) {
                return true;
            }
            public void verify(String arg0, SSLSocket arg1) throws IOException {}
            public void verify(String arg0, String[] arg1, String[] arg2) throws SSLException {}
            public void verify(String arg0, X509Certificate arg1) throws SSLException {}
        };
        try {
            //TLS1.0與SSL3.0基本上沒有太大的差別,可粗略理解爲TLS是SSL的繼承者,但它們使用的是相同的SSLContext
            SSLContext ctx = SSLContext.getInstance("TLS");
            //使用TrustManager來初始化該上下文,TrustManager只是被SSL的Socket所使用
            ctx.init(null, new TrustManager[] { xtm }, null);
            //創建SSLSocketFactory
            SSLSocketFactory socketFactory = new SSLSocketFactory(ctx);
            socketFactory.setHostnameVerifier(hostnameVerifier);
            //通過SchemeRegistry將SSLSocketFactory註冊到我們的HttpClient上
            httpClient.getConnectionManager().getSchemeRegistry().register(new Scheme("https", socketFactory, 443));
            HttpPost httpPost = new HttpPost(url);
            List<NameValuePair> formParams = new ArrayList<NameValuePair>(); // 構建POST請求的表單參數
            for (Map.Entry<String, String> entry : params.entrySet()) {
                formParams.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
            }
            httpPost.setEntity(new UrlEncodedFormEntity(formParams, "UTF-8"));
            HttpResponse response = httpClient.execute(httpPost);
            HttpEntity entity = response.getEntity(); // 獲取響應實體
            if (entity != null) {
                responseContent = EntityUtils.toString(entity, "UTF-8");
            }
        } catch (KeyManagementException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (ClientProtocolException e) {
            e.printStackTrace();
        } catch (ParseException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 關閉連接,釋放資源
            httpClient.getConnectionManager().shutdown();
        }
        return responseContent;
    }

爲了大家不引錯包,我把import也放在這裏:

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
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
 
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
 
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.ParseException;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.conn.ssl.X509HostnameVerifier;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;


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