如何忽略Apache HttpClient 4.0中的SSL证书错误

166

如何使用Apache HttpClient 4.0绕过无效的SSL证书错误?


12
需要注意的是,对于这个问题的答案并没有超出问题要求的范围:它们只是让你忽略错误,而不是修复潜在的问题(有点像从烟雾报警器中拆下电池而不是扑灭火源)。证书的目的在于确保 SSL/TLS 连接的安全性,忽略这些错误会引入 MITM 攻击的漏洞。使用测试证书而不是忽略错误。 - Bruno
1
与https://dev59.com/-nI-5IYBdhLWcg3wfoW1相关的内容 - Gray
58
“像拆除烟雾报警器的电池”这句话的意思是,你可能会默认其他开发者知道他们在做什么。也许问题的动机是本地测试,而提问者希望运行一个快速测试,而不必通过设置甚至一个简单的SSL环境所需的大量Java样板文件。也许有人可以直接回答问题,而不必进行“比我更高尚”的演讲。请注意,该翻译尽量保持原文的意思和风格,同时使其更加通俗易懂。 - Mike
4
无法在处理小型厨房火灾期间关闭烟雾探测器30-60分钟,显示某些法律官员对使用模式的极度缺乏洞察力,这让我感觉近乎犯罪。事实上,有一个"从烟雾报警器中取出电池"的概念证明了这一点。我对为一个简单的测试而需要获得证书感到同样愤怒,我知道这不会涉及安全问题。这个问题的存在就证明了这一点。 - Bill K
如果您不想要安全性,为什么还要使用SSL呢? - user207421
显示剩余4条评论
25个回答

125

其他所有答案都已被弃用或无法在HttpClient 4.3中使用。

以下是一种构建http客户端并允许所有主机名的方法。

CloseableHttpClient httpClient = HttpClients
    .custom()
    .setHostnameVerifier(new AllowAllHostnameVerifier())
    .build();

如果您正在使用4.4版本或更高版本,则更新后的调用看起来像这样:

CloseableHttpClient httpClient = HttpClients
    .custom()
    .setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE)
    .build();

谢谢您的回答,我想知道HttpsClients是从哪个包中来的,因为我在使用Android compile("org.apache.httpcomponents:httpclient:4.3.4"),但是这个类并没有出现。 - Juan Saravia
1
它的包是org.apache.http.impl.client.HttpClients。 - Eric
20
这个方法解决了主机名不匹配的问题(我猜测),但当证书没有被可信机构签名时,它似乎并不起作用。 - twm
1
@twm 这就是为什么它说“允许所有主机名”,信任问题需要不同的配置。 - eis
1
@eis,我指出这个答案在某些情况下回答了原问题,但在其他情况下则没有。 - twm

98

您需要使用自己的TrustManager创建SSLContext,并使用此上下文创建HTTPS方案。以下是代码:

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

// set up a TrustManager that trusts everything
sslContext.init(null, new TrustManager[] { new X509TrustManager() {
            public X509Certificate[] getAcceptedIssuers() {
                    System.out.println("getAcceptedIssuers =============");
                    return null;
            }

            public void checkClientTrusted(X509Certificate[] certs,
                            String authType) {
                    System.out.println("checkClientTrusted =============");
            }

            public void checkServerTrusted(X509Certificate[] certs,
                            String authType) {
                    System.out.println("checkServerTrusted =============");
            }
} }, new SecureRandom());

SSLSocketFactory sf = new SSLSocketFactory(sslContext);
Scheme httpsScheme = new Scheme("https", 443, sf);
SchemeRegistry schemeRegistry = new SchemeRegistry();
schemeRegistry.register(httpsScheme);

// apache HttpClient version >4.2 should use BasicClientConnectionManager
ClientConnectionManager cm = new SingleClientConnManager(schemeRegistry);
HttpClient httpClient = new DefaultHttpClient(cm);

21
嗯,它告诉我 'new SSLSocketFactory(ssslCont)' 需要一个 KeyStore,而不是 SSLContext。我有什么遗漏吗? - MSpeed
3
我收到了一个错误,说无法将X509TrustManager转换为TrustManager。 - MW.
4
请确保导入正确的包,即从 org.apache.http 中导入。 - rantoniuk
1
在4.3中,创建和配置SSLContext的方式已经进行了修订:SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(null, new TrustSelfSignedStrategy()).useTLS().build(); - Jason
3
有人知道如何使用 HttpClientBuilder 将所有这些东西组合起来吗? - Ali
显示剩余7条评论

84

Apache HttpClient 4.5.5

HttpClient httpClient = HttpClients
            .custom()
            .setSSLContext(new SSLContextBuilder().loadTrustMaterial(null, TrustAllStrategy.INSTANCE).build())
            .setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE)
            .build();

未使用任何弃用 API。

简单可验证的测试用例:

package org.apache.http.client.test;

import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContextBuilder;
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;

public class ApacheHttpClientTest {

    private HttpClient httpClient;

    @Before
    public void initClient() throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException {
        httpClient = HttpClients
                .custom()
                .setSSLContext(new SSLContextBuilder().loadTrustMaterial(null, TrustAllStrategy.INSTANCE).build())
                .setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE)
                .build();
    }

    @Test
    public void apacheHttpClient455Test() throws IOException {
        executeRequestAndVerifyStatusIsOk("https://expired.badssl.com");
        executeRequestAndVerifyStatusIsOk("https://wrong.host.badssl.com");
        executeRequestAndVerifyStatusIsOk("https://self-signed.badssl.com");
        executeRequestAndVerifyStatusIsOk("https://untrusted-root.badssl.com");
        executeRequestAndVerifyStatusIsOk("https://revoked.badssl.com");
        executeRequestAndVerifyStatusIsOk("https://pinning-test.badssl.com");
        executeRequestAndVerifyStatusIsOk("https://sha1-intermediate.badssl.com");
    }

    private void executeRequestAndVerifyStatusIsOk(String url) throws IOException {
        HttpUriRequest request = new HttpGet(url);

        HttpResponse response = httpClient.execute(request);
        int statusCode = response.getStatusLine().getStatusCode();

        assert statusCode == 200;
    }
}

2
谢谢!只需在此答案中将 TrustAllStrategy.INSTANCE 更改为 TrustSelfSignedStrategy.INSTANCE - Percy Vega
这对我没有用。javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX路径构建失败:sun.security.provider.certpath.SunCertPathBuilderException:无法找到所请求目标的有效认证路径。 - ggb667
2
@ggb667 我找不到 TrustAllStrategy.INSTANCE,但我尝试自己实现它:new SSLContextBuilder().loadTrustMaterial(null, (chain, authType) -> true).build(),并且它已经起作用了。 - Svichkarev Anatoly
在4.5.6的apache依赖中,.setSSLContext()实际上会抛出一个错误?将其更改为.setSslContext()可以消除错误吗?除非他们在4.5.6中进行了更改,否则应该使用小写的“Ssl”,即.setSslContext()。 - whyoz
1
这个答案有效,但没有 @SvichkarevAnatoly 的评论就不行。感谢你们两个!! - WISERDIVISOR

62

我最近使用了更新的HttpClient 4.5,发现自从4.4版本之后,一些功能已经被弃用了。以下是适用于我并使用了最新API的代码片段:

final SSLContext sslContext = new SSLContextBuilder()
        .loadTrustMaterial(null, (x509CertChain, authType) -> true)
        .build();

return HttpClientBuilder.create()
        .setSSLContext(sslContext)
        .setConnectionManager(
                new PoolingHttpClientConnectionManager(
                        RegistryBuilder.<ConnectionSocketFactory>create()
                                .register("http", PlainConnectionSocketFactory.INSTANCE)
                                .register("https", new SSLConnectionSocketFactory(sslContext,
                                        NoopHostnameVerifier.INSTANCE))
                                .build()
                ))
        .build();

对于httpclient 4.5.2,这对我也起作用。 - Vikas Ranjan
这个是针对HttpClient 4.5最新版本的。 - You're awesome
回答问题并且有效期至2023年,使用最新版本的HttpClient。 - dilesh yadav

34

仅作记录,使用HttpClient 4.1可以更简单地实现同样的功能。

    SSLSocketFactory sslsf = new SSLSocketFactory(new TrustStrategy() {

        public boolean isTrusted(
                final X509Certificate[] chain, String authType) throws CertificateException {
            // Oh, I am easy...
            return true;
        }

    });

1
这个示例中是否缺少一些代码?也许是对httpClient.set...的调用? - Gray
6
httpclient.getConnectionManager().getSchemeRegistry().register(new Scheme("https", 443, sslsf));这行代码的意思是使用 HttpClient 库,注册一个名为 "https" 的协议,端口号为 443,并传入一个 SslSocketFactory 对象。 - Ben Flynn
8
在HttpClient 4.3中,SSLSocketFactory已被弃用。 - Toilal
1
如果使用Java 8,甚至可以使用 new SSLSocketFactory((chain, authType) -> true); - jlb

29

记录一下,使用 httpclient 4.3.6 进行测试,并兼容流畅 API 的 Executor。

CloseableHttpClient httpClient = HttpClients.custom().
                    setHostnameVerifier(new AllowAllHostnameVerifier()).
                    setSslcontext(new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy()
                    {
                        public boolean isTrusted(X509Certificate[] arg0, String arg1) throws CertificateException
                        {
                            return true;
                        }
                    }).build()).build();

3
对于 HttpClient 4.4 及以上版本,你需要这样做——可能还需要使用该 SSLContext 创建一个 SSLConnectionSocketFactory,并在 Registry<ConnectionSocketFactory> 中定义它,如果你要创建一个 PoolingHttpClientConnectionManager。其他答案更受欢迎,但在 HttpClient 4.4 上不起作用。 - Thomas W
1
使用httpclient-4.3.5.jar可以完全按照这种方式工作。 - Harald
适用于httpclient 4.3.2。 - Naveen Yalla

18
对于Apache HttpClient 4.4:
HttpClientBuilder b = HttpClientBuilder.create();

SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
    public boolean isTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
        return true;
    }
}).build();
b.setSslcontext( sslContext);

// or SSLConnectionSocketFactory.getDefaultHostnameVerifier(), if you don't want to weaken
HostnameVerifier hostnameVerifier = SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER;

SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext, hostnameVerifier);
Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
        .register("http", PlainConnectionSocketFactory.getSocketFactory())
        .register("https", sslSocketFactory)
        .build();

// allows multi-threaded use
PoolingHttpClientConnectionManager connMgr = new PoolingHttpClientConnectionManager( socketFactoryRegistry);
b.setConnectionManager( connMgr);

HttpClient client = b.build();

以下内容摘自我们实际的工作实现。

其他答案很受欢迎,但对于HttpClient 4.4,它们无法正常工作。我花了几个小时尝试并耗尽了可能性,但在4.4中似乎发生了极大的API更改和重定位。

请参见稍微详细的说明:http://literatejava.com/networks/ignore-ssl-certificate-errors-apache-httpclient-4-4/

希望这有所帮助!


14

如果您只想消除无效主机名错误,您可以执行以下操作:

HttpClient httpClient = new DefaultHttpClient();
SSLSocketFactory sf = (SSLSocketFactory)httpClient.getConnectionManager()
    .getSchemeRegistry().getScheme("https").getSocketFactory();
sf.setHostnameVerifier(new AllowAllHostnameVerifier());

8
自4.1版本以后, sf.setHostnameVerifier方法已被弃用。替代方案是使用其中一个构造函数。例如:SSLSocketFactory sf = new SSLSocketFactory(sslContext, SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); - kaliatech

9
我们正在使用HTTPClient 4.3.5,并尝试了stackoverflow上存在的几乎所有解决方案,但都没有成功。 在思考和解决问题后,我们得出了以下完美运行的代码, 只需在创建HttpClient实例之前添加它即可。

在进行POST请求时调用的某些方法...

SSLContextBuilder builder = new SSLContextBuilder();
    builder.loadTrustMaterial(null, new TrustStrategy() {
        @Override
        public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            return true;
        }
    });

    SSLConnectionSocketFactory sslSF = new SSLConnectionSocketFactory(builder.build(),
            SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);

    HttpClient httpClient = HttpClients.custom().setSSLSocketFactory(sslSF).build();
    HttpPost postRequest = new HttpPost(url);

继续以正常形式提交您的请求。

8

在使用fluent 4.5.2时,我需要做出以下修改才能使其正常工作。

try {
    TrustManager[] trustAllCerts = new TrustManager[] {
       new X509TrustManager() {
    public java.security.cert.X509Certificate[] getAcceptedIssuers() {
        return null;
    }
    public void checkClientTrusted(X509Certificate[] certs, String authType) {  }

    public void checkServerTrusted(X509Certificate[] certs, String authType) {  }
    }
    };

    SSLContext sc = SSLContext.getInstance("SSL");
    sc.init(null, trustAllCerts, new SecureRandom());
    CloseableHttpClient httpClient = HttpClients.custom().setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE).setSslcontext(sc).build();

    String output = Executor.newInstance(httpClient).execute(Request.Get("https://127.0.0.1:3000/something")
                                      .connectTimeout(1000)
                                      .socketTimeout(1000)).returnContent().asString();
    } catch (Exception e) {
    }

1
这是唯一对我有效的解决方案。在升级到4.5并尝试此方法之前,我尝试了上述针对4.3和4.4的解决方案。 - YoungDinosaur

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接