У меня есть API, с которым я пытаюсь выполнить интеграцию, и этот API поддерживает только TLSv1.3. Я попробовал несколько различных стратегий подключения, но без каких-либо реальных изменений и надеялся, что кто-нибудь укажет мне правильное направление. Вот пример кода, иллюстрирующий мою проблему:
import javax.net.ssl.SSLContext;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Main {
public static void main(String[] args) {
String apiUrl = "https://api-url.com";
try {
// Create an SSLContext with TLS 1.3
SSLContext sslContext = SSLContext.getInstance("TLSv1.3");
sslContext.init(null, null, null);
// Create an HttpClient with the SSLContext
HttpClient httpClient = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2)
.sslContext(sslContext)
.build();
// Create an HttpRequest
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(apiUrl))
.GET()
.header("Accept", "application/json")
.build();
// Send the request and retrieve the response
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
// Process the response
if (response.statusCode() == 200) {
String responseBody = response.body();
System.out.println("Response Body: " + responseBody);
} else {
System.out.println("Request failed. Status code: " + response.statusCode());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
Этот код работает, когда это автономное приложение успешно подключается к API. Когда я беру этот же код и помещаю его в службу в своем приложении весенней загрузки, происходит сбой из-за сбоя подтверждения SSL:
javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:130)
at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:117)
at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:365)
at java.base/sun.security.ssl.Alert$AlertConsumer.consume(Alert.java:287)
at java.base/sun.security.ssl.TransportContext.dispatch(TransportContext.java:204)
at java.base/sun.security.ssl.SSLTransport.decode(SSLTransport.java:172)
at java.base/sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1507)
Я вижу ClientHello, когда у меня включена отладка, и он явно отправляет TLSv1.2:
javax.net.ssl|DEBUG|F6|XNIO-1 task-1|2024-04-03 23:07:47.320 EDT|ClientHello.java:640|Produced ClientHello handshake message (
"ClientHello": {
"client version" : "TLSv1.2",
"random" : "FDA9B3D29A6C9F53E773999AFB00B22C4CB2D6103266BDE2B783DF2CBC8A82BA",
"session id" : "A60300E77225F0C6A66FF1D40A90ED38CBC5559DF77BCF7EC5E7A65999109417",
"cipher suites" : "[TLS_AES_256_GCM_SHA384(0x1302), TLS_AES_128_GCM_SHA256(0x1301), TLS_CHACHA20_POLY1305_SHA256(0x1303)]",
"compression methods" : "00",
Используя OpenSSL для подключения с моего терминала, я вижу, что требуется TLSv1.3.
SSL handshake has read 2632 bytes and written 418 bytes
Verification: OK
---
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
Server public key is 256 bit
This TLS version forbids renegotiation.
Compression: NONE
Expansion: NONE
No ALPN negotiated
Early data was not sent
Verify return code: 0 (ok)
---
С помощью System.GetProperty я проверил, что jdk.tls.client.protocols и https.protocols не установлены, поэтому используются значения по умолчанию. Я также попробовал установить для них обоих значение TLSv1.3, но при выполнении запроса он по-прежнему использует версию 1.2.
Это приложение также использует Java 11, который, как я подтвердил, поддерживает TLSv1.3 по умолчанию. Я также запускал его с использованием Java 22 (который, к моему удивлению, действительно работал), но у меня и там та же проблема.
Я немного заржавел в Java и не СУПЕР знаком с пружинной загрузкой в целом, но есть ли что-то, чего мне здесь не хватает, чтобы заставить эту штуку использовать TLSv1.3 при создании исходящего соединения?
Это имеет смысл, и я вижу, что это происходит в автономном приложении. Есть ли более глубокий способ отладки этого? В автономном приложении с включенной отладкой я сразу вижу ServerHello и остальную часть переговоров, в которых согласовывается TLSv1.3. Однако внутри приложения весенней загрузки после ClientHello оно просто получает предупреждение о сбое рукопожатия и никакой дополнительной информации не требуется.
Спасибо Штеффену Ульриху в комментариях к ServerFault, который указал, что SNI может вызвать эту проблему. Я обнаружил, что в другом месте этого Java-приложения оно отключено:
System.setProperty("jsse.enableSNIExtension", "false");
исправление этого, установив для него значение true, устраняет мою непосредственную проблему.
AFAIK, версия весенней загрузки здесь не имеет значения, важнее, какую версию JDK вы используете. Кроме того: будьте очень осторожны при принятии решения о том, какая версия TLS используется на основе первоначального рукопожатия, потому что это сбивает с толку: TLSv 1.3 требует, чтобы в некоторых местах утверждалось, что это 1.2, а в других - сообщали о способности говорить 1.3, просто потому, что в одном месте это говорит "1,2" не значит, что это 1,2: Объяснение смотрите здесь.