在OkHttp中信任自签名证书

2025/03/30

1. 概述

在本文中,我们将了解如何初始化和配置OkHttpClient以信任自签名证书。为此,我们将设置一个由自签名证书保护的最小HTTPS Spring Boot应用程序。

2. 基础知识

在深入研究实现此功能的代码之前,我们先来了解一下基本原理。SSL的本质是它在任意两方(通常是客户端和服务器)之间建立安全连接,此外,它还有助于保护通过网络传输的数据的隐私性和完整性。

JSSE API是Java SE的一个安全组件,为SSL/TLS协议提供了完整的API支持。

SSL证书(又称数字证书)在建立TLS握手、促进通信方之间的加密和信任方面发挥着至关重要的作用。自签名SSL证书不是由知名且受信任的证书颁发机构(CA)颁发的证书,开发人员可以轻松生成和签名这些证书,以便为其软件启用HTTPS。

由于自签名证书不可信,因此浏览器和标准HTTPS客户端(如OkHttpApache HTTP Client)默认都不信任它们

最后,我们可以使用Web浏览器或OpenSSL命令行实用程序方便地获取服务器证书

3. 设置测试环境

为了演示应用程序使用OkHttp接受和信任自签名证书,让我们快速配置并启动一个启用了HTTPS(由自签名证书保护)的简单Spring Boot应用程序。

默认配置将启动一个监听8443端口的Tomcat服务器,并公开一个可通过“https://localhost:8443/welcome”访问的安全REST API。

现在,让我们使用OkHttp客户端向该服务器发出HTTPS请求并使用“/welcome” API。

4. OkHttpClient和SSL

本节将初始化OkHttpClient并使用它来连接到我们刚刚设置的测试环境。此外,我们将检查路径中遇到的错误,并逐步实现使用OkHttp信任自签名证书的最终目标。

首先,让我们为OkHttpClient创建一个构建器:

OkHttpClient.Builder builder = new OkHttpClient.Builder();

另外,让我们声明本教程中将使用的HTTPS URL:

int SSL_APPLICATION_PORT = 8443;
String HTTPS_WELCOME_URL = "https://localhost:" + SSL_APPLICATION_PORT + "/welcome";

4.1 SSLHandshakeException

如果没有为SSL配置OkHttpClient,当我们尝试使用HTTPS URL时,就会出现安全异常:

@Test(expected = SSLHandshakeException.class)
public void whenHTTPSSelfSignedCertGET_thenException() {
    builder.build()
            .newCall(new Request.Builder()
                    .url(HTTPS_WELCOME_URL).build())
            .execute();
}

堆栈跟踪如下:

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

上述错误恰恰意味着服务器使用了未经证书颁发机构(CA)签名的自签名证书。

因此,客户端无法验证直至根证书的信任链,所以它抛出了SSLHandshakeException

4.2 SSLPeerUnverifiedException

现在,让我们配置信任证书的OkHttpClient,无论其性质如何-CA签名或自签名。

首先,我们需要创建自己的TrustManager,以使默认证书验证无效,并用我们的自定义实现覆盖这些验证:

TrustManager TRUST_ALL_CERTS = new X509TrustManager() {
    @Override
    public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) {
    }

    @Override
    public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) {
    }

    @Override
    public java.security.cert.X509Certificate[] getAcceptedIssuers() {
        return new java.security.cert.X509Certificate[] {};
    }
};

接下来,我们将使用上面的TrustManager初始化SSLContext,并设置OkHttpClient构建器的SSLSocketFactory

SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, new TrustManager[] { TRUST_ALL_CERTS }, new java.security.SecureRandom());
builder.sslSocketFactory(sslContext.getSocketFactory(), (X509TrustManager) TRUST_ALL_CERTS);

再次,让我们运行测试。不难想象,即使经过上述调整,使用HTTPS URL仍会引发错误:

@Test(expected = SSLPeerUnverifiedException.class)
public void givenTrustAllCerts_whenHTTPSSelfSignedCertGET_thenException() {
    // initializing the SSLContext and set the sslSocketFactory
    builder.build()
            .newCall(new Request.Builder()
                    .url(HTTPS_WELCOME_URL).build())
            .execute();
}

确切的错误是:

javax.net.ssl.SSLPeerUnverifiedException: Hostname localhost not verified:
    certificate: sha256/bzdWeeiDwIVjErFX98l+ogWy9OFfBJsTRWZLB/bBxbw=
    DN: CN=localhost, OU=localhost, O=localhost, L=localhost, ST=localhost, C=IN
    subjectAltNames: []

这是由于一个众所周知的问题-主机名验证失败大多数HTTP库都会根据证书的SubjectAlternativeName的DNS名称字段执行主机名验证,而该字段在服务器的自签名证书中不可用,如上图详细的堆栈跟踪所示。

4.3 覆盖HostnameVerifier

正确配置OkHttpClient的最后一步是禁用默认的HostnameVerifier,并将其替换为另一个绕过主机名验证的HostnameVerifier。

让我们添加最后一部分自定义:

builder.hostnameVerifier(new HostnameVerifier() {
    @Override
    public boolean verify(String hostname, SSLSession session) {
        return true;
    }
});

现在,让我们最后一次运行测试:

@Test
public void givenTrustAllCertsSkipHostnameVerification_whenHTTPSSelfSignedCertGET_then200OK() {
    // initializing the SSLContext and set the sslSocketFactory
    // set the custom hostnameVerifier
    Response response = builder.build()
            .newCall(new Request.Builder()
                    .url(HTTPS_WELCOME_URL).build())
            .execute();
    assertEquals(200, response.code());
    assertNotNull(response.body());
    assertEquals("<h1>Welcome to Secured Site</h1>", response.body()
            .string());
}

最后,OkHttpClient能够成功使用由自签名证书保护的HTTPS URL

5. 总结

在本教程中,我们学习了如何为OkHttpClient配置SSL,以便它能够信任自签名证书并使用任何HTTPS URL。

然而,需要考虑的一点是,尽管这种设计完全省略了证书验证和主机名验证,但客户端和服务器之间的所有通信仍然是加密的。双方之间的信任虽然丢失了,但SSL握手和加密并没有受到影响。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章