天天看點

記錄一次線上Https請求報錯的問題

進入正題:

        我們系統依賴一個外部查詢接口,是HTTPS提供服務的,由于外部系統沒有測試環境,是以我們都是直接請求的他們生産環境。項目裡面我們使用的HttpClient,其建立SSLClient的代碼如下:

private static CloseableHttpClient createSSLClientDefault() {        SSLContext sslContext;
        try {
            sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
                //信任所有
                @Override
                public boolean isTrusted(X509Certificate[] xcs, String string) {
                    return true;
                }
            }).build();


            SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext);
            return HttpClients.custom().setSSLSocketFactory(sslsf).build();
        } catch (Exception ex) {
            logger.error(ex.getMessage(), ex);
        }
        return HttpClients.createDefault();
    }
           

這樣就可以正常的調用HTTPS服務了。但是當我們的服務部署到線上環境以後,由于生産機器找不到外部接口的域名,是以配置了host,指向該外部接口所在的Nginx伺服器,但是請求一直報錯:

doesn't match any of the subject alternative names: []

。用

curl -k

調用該接口,報

(35) SSL connect error

錯誤,當初還一度懷疑linux機器openssl版本問題,是以強制更新了一個版本還是沒有解決。後面知道curl有個

-v

的參數,錯誤資訊如下:

* Initializing NSS with certpath: sql:/etc/pki/nssdb*   CAfile: /etc/pki/tls/certs/ca-bundle.crt
  CApath: none
* NSS error -5990
* Closing connection #0
* SSL connect error
curl: (35) SSL connect error
           

是以按照網上的流程更新了一下nss版本,最終curl -k可以調通服務了。但是通過httpclient還是報錯,是以可以排除掉openssl的問題。是以隻能分析下測試環境和生産環境的差異,看問題到底出現在什麼地方。

        後面通過咨詢外部接口的同僚,了解到他們的外部接口服務接入了公司内部的智能網關,在智能網關配置了統一的HTTPS證書。而且隻要接入了智能網關的服務,請求都會先經過智能網關,然後轉發到他們自己的伺服器。但是生産環境和智能網關不在同一網絡,而且解析不到外部接口的域名,隻能通過host映射到他們的Nginx伺服器上。看一下測試環境請求外部接口的網絡拓撲:

記錄一次線上Https請求報錯的問題

測試環境請求網絡拓撲

生産環境的請求網絡拓撲:

記錄一次線上Https請求報錯的問題

生産環境請求網絡拓撲

        總算有點眉目了,測試環境能調通主要是能在内部DNS伺服器解析到對應域名。而生産環境是通過host來做的映射,當去解析域名的時候發現目前網絡沒有該域名,是以報錯

doesn't match any of the subject alternative names: []

。當時有兩種思路,第一種:能不能在生産環境開啟通路智能網關的權限,然後直接走域名,但是公司不知道基于什麼考慮,不允許這樣做。第二種:http client是否支援不解析域名的操作,确實http client可以設定不解析host的政策,将其建立SSLClient的代碼調整為如下代碼:

private static CloseableHttpClient createSSLClientDefault() {        try {
            SSLContextBuilder builder = new SSLContextBuilder();
            builder.loadTrustMaterial(null, new TrustSelfSignedStrategy());
            //不進行主機名驗證
            SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(builder.build(),
                    NoopHostnameVerifier.INSTANCE);
            Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
                    .register("http", new PlainConnectionSocketFactory())
                    .register("https", sslConnectionSocketFactory)
                    .build();


            PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(registry);
            cm.setMaxTotal(100);
            CloseableHttpClient httpclient = HttpClients.custom()
                    .setSSLSocketFactory(sslConnectionSocketFactory)
                    .setDefaultCookieStore(new BasicCookieStore())
                    .setConnectionManager(cm).build();
            return httpclient;
        } catch (Exception e) {
            logger.error("createSSLClientDefault error", e);
        }
        return null;
    }
           

即可解決問題。一個https的小問題确實耗費了我不少的時間,記錄一下。