1. 概述
在本文中,我们将了解如何初始化和 配置 OkHttpClient 以信任自签名证书 。为此,我们将设置一个最小的启用 HTTPS 的 Spring Boot 应用程序,并通过自签名证书进行保护。
有关该库的更多详细信息,请参阅我们关于 OkHttp 的文章集。
2. 基本原理
在我们深入研究负责完成这项工作的代码之前,让我们先了解一下底线。 SSL 的本质是在任意两方(通常是客户端和服务器)之间建立安全连接 。此外,它还有助于 保护通过网络传输的数据的隐私和完整性 。
JSSE API是 Java SE 的安全组件,为 SSL/TLS 协议提供完整的 API 支持。
SSL 证书(又名数字证书)在建立 TLS 握手、促进通信双方之间的加密和信任方面发挥着至关重要的作用。自签名 SSL 证书不是由知名且受信任的证书颁发机构 (CA) 颁发的。开发人员可以轻松生成并签署它们,以便为其软件启用 HTTPS。
由于自签名证书不可信,浏览器和标准 HTTPS 客户端(例如OkHttp和Apache HTTP Client)默认情况下都不信任它们 。
最后,我们可以使用网络浏览器或 OpenSSL 命令行实用程序方便地获取服务器证书。
3. 设置测试环境
为了演示使用 OkHttp 接受和信任自签名证书的应用程序,让我们快速配置并启动一个启用了 HTTPS 的简单 Spring Boot 应用程序(由自签名证书保护)。
默认配置将启动 Tomcat 服务器侦听端口 8443 并公开可通过 “ 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.覆盖 主机名验证器
正确配置 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 握手和加密不会受到损害。
与往常一样,我们可以在 GitHub 上找到完整的源代码。