#java #spring-boot #ssl #okhttp
#java #пружинный загрузчик #ssl #okhttp
Вопрос:
У меня есть приложение с весенней загрузкой (версия 2.3.7), которое использует okhttp3, и я пытаюсь вызвать api 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 обновлен, нужно ли мне писать дополнительный код для проверки имени хоста?
Чтобы быть более конкретным, у меня есть следующий фрагмент. Если я передам truststore в параметрах виртуальной -Djavax.net.ssl.trustStore=client-truststore.jks -Djavax.net.ssl.trustStorePassword=changeit
машины, вызов с okhttp завершится неудачно, но с шаблоном rest завершится успешно.
@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();
}
Вывод приведенного выше фрагмента выглядит следующим образом
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"]>
Ответ №1:
Я подозреваю, что это связано с тем, что вы генерируете недопустимые сертификаты, CN больше не следует использовать для проверки имени хоста. subjectAltNames пусто в вашем сертификате. Это изменилось в 3.10.0
https://square.github.io/okhttp/changelog_3x/#version-3100
Новое: не возвращайтесь к проверке общих имен (CN) для имен хостов. Это поведение устарело с RFC 2818 в мае 2000 года и недавно было удалено из основных веб-браузеров.
Если вы просто хотите внести в белый список один сервер разработки, вы можете использовать следующее
https://square.github.io/okhttp/changelog/#version-470
val clientCertificates = HandshakeCertificates.Builder()
.addPlatformTrustedCertificates()
.addInsecureHost("localhost")
.build()
val client = OkHttpClient.Builder()
.sslSocketFactory(clientCertificates.sslSocketFactory(), clientCertificates.trustManager)
.build()
Комментарии:
1. Сертификат действительно не имеет никаких subjectAltNames и с самой старой версией OkHttp работал нормально. Большое спасибо @yuri.
Ответ №2:
По-видимому, единственный способ сделать это — заставить 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();
Комментарии:
1. Это способ сделать это, если (как и я) вы используете старую версию okhttp, старше 4.7.0