如何避免 javax.net.ssl.SSLPeerUnverifiedException:主机名 test.server.com 未验证

Ale*_*ros 7 java ssl okhttp spring-boot

我有一个使用 okhttp3 的 spring-boot 应用程序(版本 2.3.7),我试图在https://test.server.com:8888/api. 服务器上的证书是自签名的,因此我已更新我的插入符以信任此证书。当我像这样运行 SSLPoke.class 时,java SSLPoke test.server.com 8888我得到了Successfully Connected。但是当我的应用程序发出请求时,我收到错误javax.net.ssl.SSLPeerUnverifiedException: Hostname test.server.com not verified:。由于 cacerts 已更新,我是否需要编写额外的代码来验证主机名?

更具体地说,我有以下片段。如果我在虚拟机选项中传递信任库,-Djavax.net.ssl.trustStore=client-truststore.jks -Djavax.net.ssl.trustStorePassword=changeit则使用 okhttp 的调用会失败,但使用其余模板会成功。

@SpringBootApplication
public class DemoOkhttpClientApplication implements CommandLineRunner {

    public static void main(String[] args) {
        SpringApplication.run(DemoOkhttpClientApplication.class, args);
    }

    @Override
    public void run(String... args) throws Exception {
        try {
            Retrofit retrofit = new Retrofit.Builder()
                    .baseUrl("https://localhost:8443/")
                    .addConverterFactory(MoshiConverterFactory.create())
                    .build();
            TestClient testClient = retrofit.create(TestClient.class);
            System.out.println(String.format("OkHttp Response %s", testClient.callTestEndpoint().execute().body()));
        } catch (Exception ex){
            System.err.println();
            System.err.println("Ok Http error " + ex.getMessage());
        }

        try {
            RestTemplate restTemplate = new RestTemplate();
            System.out.println(String.format("Rest Template Response %s",
                    restTemplate.exchange("https://localhost:8443/test", HttpMethod.GET, null, String.class)));
        } catch (Exception ex){
            System.err.println("Rest Template error" + ex.getMessage());
        }
    }
}

interface TestClient {
    @GET("/test")
    Call<String> callTestEndpoint();
}

Run Code Online (Sandbox Code Playgroud)

上面代码片段的输出是这样的

Ok Http error Hostname localhost not verified:
    certificate: sha256/INkKXJiMFIGNnvE5ga1Ye0KjxjP5jO9hIrNvQs4wuU0=
    DN: CN=localhost, OU=PC, O=PC, L=Marousi, ST=Athens, C=GR
    subjectAltNames: []
Rest Template Response <200,Ok,[Content-Type:"text/plain;charset=UTF-8", Content-Length:"2", Date:"Thu, 11 Feb 2021 17:09:55 GMT", Keep-Alive:"timeout=60", Connection:"keep-alive"]>

Run Code Online (Sandbox Code Playgroud)

Yur*_*mke 7

我怀疑这是因为您生成了无效的证书,CN 不应再用于主机名验证。您的证书中的 subjectAltNames 为空。这在 3.10.0 中发生了变化

\n

https://square.github.io/okhttp/changelog_3x/#version-3100

\n
\n

新增内容:Don\xe2\x80\x99t 回退到主机名的通用名称 (CN) 验证。此行为已于 2000 年 5 月随 RFC 2818 弃用,并且最近已从主要 Web 浏览器中删除。

\n
\n

如果您只想将一台开发服务器列入白名单,您可以使用以下命令

\n

https://square.github.io/okhttp/changelog/#version-470

\n
val clientCertificates = HandshakeCertificates.Builder()\n    .addPlatformTrustedCertificates()\n    .addInsecureHost("localhost")\n    .build()\n\nval client = OkHttpClient.Builder()\n    .sslSocketFactory(clientCertificates.sslSocketFactory(), clientCertificates.trustManager)\n    .build()\n
Run Code Online (Sandbox Code Playgroud)\n


Ale*_*ros 4

显然唯一的方法是让 OkHttp 忽略主机名验证

 OkHttpClient okHttpClient = new OkHttpClient.Builder().hostnameVerifier(new HostnameVerifier() {
                @Override
                public boolean verify(String hostname, SSLSession session) {
                    return true;
                }
            }).build();
            Retrofit retrofit = new Retrofit.Builder()
                    .client(okHttpClient)
                    .baseUrl("https://localhost:8443/")
                    .addConverterFactory(MoshiConverterFactory.create())
                    .build();
Run Code Online (Sandbox Code Playgroud)