Java, Spring, RestTemplate / WebClient, how to do client certificate authentication properly?

32 views Asked by At

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);
  }
0

There are 0 answers