I have to perform client certificate authentication from Spring, Java 21 env. The code below worked with Java 11, Spring 5 and WebClient. With Spring 6, Java 21 I simply cannot get it running, neither with WebClient nor with RestTemplate. The interesting thing is that no exception is thrown, the certificate itself is read up, and when debugging I can see the contents of the jks. However the receiving party - same, deployed app for all versions - sees that the requests made with Java21 are completely missing the client cert. I've tried RestTemplate also, see the code below.
This code is not working on Java 21:
public WebClient createWebClient(String keystorePath, String keystorePassword, String keyPassword)
throws Exception {
// Load the keystore
KeyStore keyStore = KeyStore.getInstance("PKCS12");
try (InputStream keystoreInputStream = new FileInputStream(keystorePath)) {
keyStore.load(keystoreInputStream, keystorePassword.toCharArray());
}
// Initialize KeyManagerFactory with the keystore
KeyManagerFactory keyManagerFactory =
KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, keyPassword.toCharArray());
// Configure the SSL context with the key managers from the KeyManagerFactory
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagerFactory.getKeyManagers(), null, null);
// Configure HttpClient to use the SSL context
HttpClient httpClient =
HttpClient.create()
.secure(
sslSpec -> {
try {
sslSpec.sslContext(
SslContextBuilder.forClient().keyManager(keyManagerFactory).build());
} catch (SSLException e) {
throw new RuntimeException(e);
}
})
.wiretap(true);
// Create the WebClient using the configured HttpClient
ClientHttpConnector connector = new ReactorClientHttpConnector(httpClient);
return WebClient.builder()
.baseUrl(statusUrl)
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.clientConnector(connector)
.build();
}
Neither this one:
private WebClient buildCustomWebClient(){
HttpClient client = HttpClient.create().secure(spec -> spec.sslContext(createSSLContext()));
ClientHttpConnector connector = new ReactorClientHttpConnector(client);
return WebClient.builder()
.baseUrl(statusUrl)
.clientConnector(connector)
.build();
}
private SslContext createSSLContext() {
try {
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(getClass().getClassLoader().getResourceAsStream(jksLocation), jksPassword.toCharArray());
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
keyManagerFactory.init(keyStore, mtJksPassword.toCharArray());
return SslContextBuilder.forClient()
.keyManager(keyManagerFactory)
.build();
}
catch (Exception e) {
throw new RuntimeException("Error creating SSL context.");
}
}
But this code worked in Java 11:
try (InputStream in = new FileInputStream(ResourceUtils.getFile(mtJksLocation))) {
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(in, jksPassword.toCharArray());
KeyManagerFactory factory =
KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
factory.init(keyStore, jksPassword.toCharArray());
SslContext sslContext = SslContextBuilder.forClient().keyManager(factory).build();
HttpClient httpClient =
HttpClient.create().secure(sslContextSpec -> sslContextSpec.sslContext(sslContext));
ClientHttpConnector clientHttpConnector = new ReactorClientHttpConnector(httpClient);
this.webClient =
WebClient.builder()
.baseUrl(statusUrl)
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.clientConnector(clientHttpConnector)
.build();
} catch (IOException
| CertificateException
| UnrecoverableKeyException
| KeyStoreException
| NoSuchAlgorithmException e) {
log.error(e.getMessage(), e);
}
RestTemplate, Java 21 (using httpclient5), not working:
public RestTemplate restTemplate() throws Exception {
SSLContext sslContext = SSLContextBuilder.create()
.loadKeyMaterial(loadKeyStore(jksLocation, jksPassword), jksPassword.toCharArray())
.build();
Registry<ConnectionSocketFactory> socketRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
.register(URIScheme.HTTPS.getId(), new SSLConnectionSocketFactory(sslContext))
.register(URIScheme.HTTP.getId(), new PlainConnectionSocketFactory())
.build();
CloseableHttpClient httpClient = HttpClientBuilder.create()
.setConnectionManager(new PoolingHttpClientConnectionManager(socketRegistry))
.setConnectionManagerShared(true)
.build();
ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
return new RestTemplate(requestFactory);
}