5

I have been stumbling through the Apache doc and other examples trying to create a client that uses Apache HttpClient to make calls to various RESTful web services. (Each of these web services potentially requires a different client certificate for authentication). Initially I have created a static code block that initialises a HttpClient (with SSLContext info and a pooling connection manager):

private static CloseableHttpClient _client;
static {
  HttpClientBuilder clientBuilder = HttpClients.custom();
  SSLContextBuilder sslContextBuilder = SSLContexts.custom();
  sslContextBuilder.loadTrustMaterial(new TrustSelfSignedStrategy());
  sslContextBuilder.loadKeyMaterial(new File("clientcert.p12"), password, password, (aliases, socket) -> aliases.keySet().iterator().next());

  SSLContext sslContext = sslContextBuilder.build();
  HostnameVerifier allowAllHosts = new NoopHostnameVerifier();
  SSLConnectionSocketFactory connectionFactory = new SSLConnectionSocketFactory(sslContext, allowAllHosts);
  clientBuilder.setSSLSocketFactory(connectionFactory);

  RegistryBuilder<ConnectionSocketFactory> regBuilder = RegistryBuilder.<ConnectionSocketFactory>create();
  regBuilder.register("https", connectionFactory);
  regBuilder.register("http", new PlainConnectionSocketFactory());
  Registry<ConnectionSocketFactory> socketFactoryRegistry = regBuilder.build();

  PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
  clientBuilder.setConnectionManager(connectionManager);
  _client = clientBuilder.build();
}

At this point I can use the client to execute requests and the client authentication works fine as long as the server is configured to allow access to clientcert.p12.

What I need is to be able to dynamically change the client certificate per request based upon the value of the required client certificate.

Is it possible to reuse a static HttpClient whilst dynamically changing the client certificate? Also if this is possible am I still going to see the performance benefit of using the pooled connection manager?

2 Answers 2

8

There is an undocumented http.socket-factory-registry execution context attribute one can use in order to override connection socket factories set up by the connection manager at construction time.

CloseableHttpClient httpClient = HttpClientBuilder.create()
        .setSSLContext(SSLContexts.createSystemDefault())
        .build();

SSLContext customSSlContext = SSLContexts.custom()
        .loadKeyMaterial(new File("my-keystore.jks"), "sectret".toCharArray(),  "sectret".toCharArray())
        .build();

Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
        .register("http", PlainConnectionSocketFactory.getSocketFactory())
        .register("https", new SSLConnectionSocketFactory(customSSlContext))
        .build();

HttpClientContext clientContext = HttpClientContext.create();
clientContext.setAttribute("http.socket-factory-registry", socketFactoryRegistry);
try (CloseableHttpResponse response = httpClient.execute(new HttpGet("https://host/stuff"), clientContext)) {
    System.out.println(response.getStatusLine());
    EntityUtils.consume(response.getEntity());
}

Use with extreme caution when using the same client instance / same connection pool to execute requests my multiple threads with different user identity / security context.

Sign up to request clarification or add additional context in comments.

1 Comment

Unsure why this question and answer don't have ALL the upvotes. Saved my life! In a microservices world, where multiple services can be called from one service, using multiple certificates, this is good to know!
4

I'm posting here my solution using PoolingHttpClientConnectionManager class, this one worked for me:

SSLConnectionSocketFactory sslConnectionFactory = new SSLConnectionSocketFactory(sslContext, new DefaultHostnameVerifier());

    Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory> create()
            .register("https", sslConnectionFactory)
            .register("http", new PlainConnectionSocketFactory())
            .build();

    PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(registry);

    CloseableHttpClient httpClient = HttpClients.custom()
            .setSSLContext(sslContext)
            .setDefaultRequestConfig(RequestConfig.custom().setCookieSpec(CookieSpecs.STANDARD).build())
            .setConnectionManager(cm)
            .build();

    cm.setMaxTotal(200);
    cm.setDefaultMaxPerRoute(20);
    final ApacheHttpClient4Engine engine = new ApacheHttpClient4Engine(httpClient);
    engine.setFollowRedirects(false);

    ResteasyClient client = clientBuilder.httpEngine(engine).build();
    ResteasyWebTarget target = client.target(UriBuilder.fromPath(
            "https://my.server.com/mtls/protected/resource"));
    String response = target.request().get(String.class);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.