Reputation: 177
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?
Upvotes: 5
Views: 5376
Reputation: 608
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);
Upvotes: 4
Reputation: 27583
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.
Upvotes: 8