package at.gv.egiz.eaaf.core.impl.http; import java.security.KeyStore; import java.security.Provider; import java.util.HashMap; import java.util.Map; import javax.annotation.Nonnull; import javax.annotation.PostConstruct; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpRequest; import org.apache.http.HttpResponse; import org.apache.http.ProtocolException; import org.apache.http.auth.AuthScope; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.CredentialsProvider; import org.apache.http.client.RedirectStrategy; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.config.Registry; import org.apache.http.config.RegistryBuilder; import org.apache.http.config.SocketConfig; import org.apache.http.conn.socket.ConnectionSocketFactory; import org.apache.http.conn.socket.LayeredConnectionSocketFactory; import org.apache.http.conn.socket.PlainConnectionSocketFactory; import org.apache.http.conn.ssl.NoopHostnameVerifier; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.impl.client.BasicCredentialsProvider; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.DefaultRedirectStrategy; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.HttpClients; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.protocol.HttpContext; import org.apache.http.ssl.SSLContexts; import org.springframework.beans.factory.annotation.Autowired; import at.gv.egiz.eaaf.core.api.idp.IConfiguration; import at.gv.egiz.eaaf.core.exceptions.EaafConfigurationException; import at.gv.egiz.eaaf.core.exceptions.EaafException; import at.gv.egiz.eaaf.core.impl.credential.EaafKeyStoreFactory; import at.gv.egiz.eaaf.core.impl.credential.KeyStoreConfiguration.KeyStoreType; import at.gv.egiz.eaaf.core.impl.data.Pair; import lombok.extern.slf4j.Slf4j; @Slf4j public class HttpClientFactory implements IHttpClientFactory { @Autowired private IConfiguration basicConfig; @Autowired private EaafKeyStoreFactory keyStoreFactory; public static final String PROP_CONFIG_CLIENT_HTTP_CONNECTION_POOL_USE = "client.http.connection.pool.use"; public static final String PROP_CONFIG_CLIENT_HTTP_CONNECTION_POOL_MAXTOTAL = "client.http.connection.pool.maxtotal"; public static final String PROP_CONFIG_CLIENT_HTTP_CONNECTION_POOL_MAXPERROUTE = "client.http.connection.pool.maxperroute"; public static final String PROP_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_SOCKET = "client.http.connection.timeout.socket"; public static final String PROP_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_CONNECTION = "client.http.connection.timeout.connection"; public static final String PROP_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_REQUEST = "client.http.connection.timeout.request"; public static final String PROP_CONFIG_CLIENT_HTTP_SSL_HOSTNAMEVERIFIER_TRUSTALL = "client.http.ssl.hostnameverifier.trustall"; public static final String PROP_CONFIG_CLIENT_MODE = "client.authmode"; public static final String PROP_CONFIG_CLIENT_AUTH_HTTP_USERNAME = "client.auth.http.username"; public static final String PROP_CONFIG_CLIENT_AUTH_HTTP_PASSORD = "client.auth.http.password"; public static final String PROP_CONFIG_CLIENT_AUTH_SSL_KEYSTORE_PATH = "client.auth.ssl.keystore.path"; public static final String PROP_CONFIG_CLIENT_AUTH_SSL_KEYSTORE_PASSORD = "client.auth.ssl.keystore.password"; private static final String PROP_CONFIG_CLIENT_AUTH_SSL_KEYSTORE_NAME = "client.auth.ssl.keystore.name"; public static final String PROP_CONFIG_CLIENT_AUTH_SSL_KEYSTORE_TYPE = "client.auth.ssl.keystore.type"; public static final String PROP_CONFIG_CLIENT_AUTH_SSL_KEY_ALIAS = "client.auth.ssl.key.alias"; public static final String PROP_CONFIG_CLIENT_AUTH_SSL_KEY_PASSWORD = "client.auth.ssl.key.password"; // default configuration values public static final String DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_SOCKET = "15"; public static final String DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_CONNECTION = "15"; public static final String DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_REQUEST = "30"; public static final String DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_POOL_MAXTOTAL = "500"; public static final String DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_POOL_MAXPERROUTE = "100"; private String defaultConfigurationId = null; private final Map availableBuilders = new HashMap<>(); /* * (non-Javadoc) * * @see at.gv.egiz.eaaf.core.impl.utils.IHttpClientFactory#getHttpClient() */ @Override public CloseableHttpClient getHttpClient() { return getHttpClient(true); } @Override public CloseableHttpClient getHttpClient(final boolean followRedirects) { return availableBuilders.get(defaultConfigurationId).setRedirectStrategy( buildRedirectStrategy(followRedirects)).build(); } @Override public CloseableHttpClient getHttpClient(@Nonnull HttpClientConfiguration config) throws EaafException { log.trace("Build http client for: {}", config.getFriendlyName()); HttpClientBuilder builder = null; if (availableBuilders.containsKey(config.getUuid())) { builder = availableBuilders.get(config.getUuid()); } else { log.debug("Initialize new http-client builder for: {}", config.getFriendlyName()); //validate configuration object config.validate(); builder = HttpClients.custom(); builder.setDefaultRequestConfig(buildDefaultRequestConfig()); //inject basic authentication infos injectBasicAuthenticationIfRequired(builder, config); //inject authentication if required final LayeredConnectionSocketFactory sslConnectionFactory = getSslContext(config); // set pool connection if required injectDefaultConnectionPoolIfRequired(builder, sslConnectionFactory); availableBuilders.put(config.getUuid(), builder); } return builder.setRedirectStrategy( buildRedirectStrategy(config.isFollowHttpRedirects())).build(); } @PostConstruct private void initalize() throws EaafException { final HttpClientConfiguration defaultHttpClientConfig = buildDefaultHttpClientConfiguration(); // initialize http client log.trace("Initializing default HTTP-Client builder ... "); final HttpClientBuilder defaultHttpClientBuilder = HttpClients.custom(); // set default request configuration defaultHttpClientBuilder.setDefaultRequestConfig(buildDefaultRequestConfig()); //inject http basic authentication injectBasicAuthenticationIfRequired(defaultHttpClientBuilder, defaultHttpClientConfig); // inject authentication if required final LayeredConnectionSocketFactory sslConnectionFactory = getSslContext(defaultHttpClientConfig); // set pool connection if required injectDefaultConnectionPoolIfRequired(defaultHttpClientBuilder, sslConnectionFactory); //set default http client builder defaultConfigurationId = defaultHttpClientConfig.getUuid(); availableBuilders.put(defaultConfigurationId, defaultHttpClientBuilder); } private HttpClientConfiguration buildDefaultHttpClientConfiguration() throws EaafConfigurationException { final HttpClientConfiguration config = new HttpClientConfiguration("Default"); // inject basic http authentication if required config.setAuthMode(basicConfig.getBasicConfiguration(PROP_CONFIG_CLIENT_MODE, HttpClientConfiguration.ClientAuthMode.NONE.getMode())); log.info("Default client authentication-mode is set to: {}", config.getAuthMode()); // set Username and Password if available config.setUsername(basicConfig.getBasicConfiguration(PROP_CONFIG_CLIENT_AUTH_HTTP_USERNAME)); config.setPassword(basicConfig.getBasicConfiguration(PROP_CONFIG_CLIENT_AUTH_HTTP_PASSORD)); // set SSL Client auth. informations if available config.buildKeyStoreConfig( basicConfig.getBasicConfiguration( PROP_CONFIG_CLIENT_AUTH_SSL_KEYSTORE_TYPE, KeyStoreType.PKCS12.getKeyStoreType()), basicConfig.getBasicConfiguration( PROP_CONFIG_CLIENT_AUTH_SSL_KEYSTORE_PATH, StringUtils.EMPTY), basicConfig.getBasicConfiguration( PROP_CONFIG_CLIENT_AUTH_SSL_KEYSTORE_PASSORD, StringUtils.EMPTY), basicConfig.getBasicConfiguration( PROP_CONFIG_CLIENT_AUTH_SSL_KEYSTORE_NAME, StringUtils.EMPTY)); config.setSslKeyAlias( basicConfig.getBasicConfiguration(PROP_CONFIG_CLIENT_AUTH_SSL_KEY_ALIAS)); config.setSslKeyPassword( basicConfig.getBasicConfiguration(PROP_CONFIG_CLIENT_AUTH_SSL_KEY_PASSWORD)); config.setDisableHostnameValidation(basicConfig.getBasicConfigurationBoolean( PROP_CONFIG_CLIENT_HTTP_SSL_HOSTNAMEVERIFIER_TRUSTALL, false)); // validate configuration object config.validate(); return config; } private void injectBasicAuthenticationIfRequired(HttpClientBuilder builder, final HttpClientConfiguration httpClientConfig) { if (httpClientConfig.getAuthMode().equals(HttpClientConfiguration.ClientAuthMode.PASSWORD)) { final CredentialsProvider provider = new BasicCredentialsProvider(); log.trace("Injecting basic authentication with username: {} and password: {}", httpClientConfig.getUsername(), httpClientConfig.getPassword()); final UsernamePasswordCredentials credentials = new UsernamePasswordCredentials( httpClientConfig.getUsername(), httpClientConfig.getPassword()); final AuthScope scope = new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT, AuthScope.ANY_REALM); provider.setCredentials(scope, credentials); builder.setDefaultCredentialsProvider(provider); log.info("Basic http authentication was injected with username: {}", httpClientConfig.getUsername()); } else { log.trace("Injection of Http Basic authentication was skipped"); } } @Nonnull private LayeredConnectionSocketFactory getSslContext(final HttpClientConfiguration httpClientConfig) throws EaafException { SSLContext sslContext = null; if (httpClientConfig.getAuthMode().equals(HttpClientConfiguration.ClientAuthMode.SSL)) { log.debug("Open keyStore with type: {}", httpClientConfig.getKeyStoreConfig().getKeyStoreType()); final Pair keyStore = keyStoreFactory.buildNewKeyStore(httpClientConfig.getKeyStoreConfig()); log.trace("Injecting SSL client-authentication into http client ... "); sslContext = HttpUtils.buildSslContextWithSslClientAuthentication(keyStore, httpClientConfig.getSslKeyAlias(), httpClientConfig.getSslKeyPassword(), httpClientConfig.isDisableTlsHostCertificateValidation(), httpClientConfig.getFriendlyName()); } else { log.trace("Initializing default SSL Context ... "); sslContext = SSLContexts.createDefault(); } // set hostname verifier HostnameVerifier hostnameVerifier = null; if (httpClientConfig.isDisableHostnameValidation()) { hostnameVerifier = new NoopHostnameVerifier(); log.warn("HTTP client-builder deactivates SSL Host-name verification!"); } final LayeredConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext, hostnameVerifier); log.debug("HTTP client-builder successfuly initialized"); return sslSocketFactory; } private void injectDefaultConnectionPoolIfRequired( HttpClientBuilder builder, final LayeredConnectionSocketFactory sslConnectionFactory) { if (basicConfig.getBasicConfigurationBoolean(PROP_CONFIG_CLIENT_HTTP_CONNECTION_POOL_USE, true)) { PoolingHttpClientConnectionManager pool; // set socketFactoryRegistry if SSLConnectionFactory is Set if (sslConnectionFactory != null) { final Registry socketFactoryRegistry = RegistryBuilder.create() .register("http", PlainConnectionSocketFactory.getSocketFactory()) .register("https", sslConnectionFactory).build(); log.trace("Inject SSLSocketFactory into pooled connection"); pool = new PoolingHttpClientConnectionManager(socketFactoryRegistry); } else { pool = new PoolingHttpClientConnectionManager(); } pool.setDefaultMaxPerRoute(Integer.parseInt( basicConfig.getBasicConfiguration(PROP_CONFIG_CLIENT_HTTP_CONNECTION_POOL_MAXPERROUTE, DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_POOL_MAXPERROUTE))); pool.setMaxTotal(Integer.parseInt( basicConfig.getBasicConfiguration(PROP_CONFIG_CLIENT_HTTP_CONNECTION_POOL_MAXTOTAL, DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_POOL_MAXTOTAL))); pool.setDefaultSocketConfig(SocketConfig.custom().setSoTimeout(Integer.parseInt( basicConfig.getBasicConfiguration(PROP_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_SOCKET, DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_SOCKET)) * 1000).build()); builder.setConnectionManager(pool); log.debug("Initalize http-client pool with, maxTotal: {} maxPerRoute: {}", pool.getMaxTotal(), pool.getDefaultMaxPerRoute()); } else if (sslConnectionFactory != null) { log.trace("Inject SSLSocketFactory without connection pool"); builder.setSSLSocketFactory(sslConnectionFactory); } } private RequestConfig buildDefaultRequestConfig() { final RequestConfig requestConfig = RequestConfig.custom() .setConnectTimeout( Integer.parseInt(basicConfig.getBasicConfiguration( PROP_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_CONNECTION, DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_CONNECTION)) * 1000) .setConnectionRequestTimeout(Integer.parseInt(basicConfig.getBasicConfiguration( PROP_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_REQUEST, DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_REQUEST)) * 1000) .setSocketTimeout(Integer.parseInt( basicConfig.getBasicConfiguration(PROP_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_SOCKET, DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_SOCKET)) * 1000) .build(); return requestConfig; } private static RedirectStrategy buildRedirectStrategy(final boolean followRedirects) { RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); if (!followRedirects) { redirectStrategy = new RedirectStrategy() { @Override public boolean isRedirected(final HttpRequest request, final HttpResponse response, final HttpContext context) throws ProtocolException { return false; } @Override public HttpUriRequest getRedirect(final HttpRequest request, final HttpResponse response, final HttpContext context) throws ProtocolException { return null; } }; } return redirectStrategy; } }