package at.gv.egiz.eaaf.core.impl.utils; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.security.KeyManagementException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; 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.conn.socket.LayeredConnectionSocketFactory; 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.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import at.gv.egiz.eaaf.core.api.idp.IConfiguration; import at.gv.egiz.eaaf.core.exceptions.EAAFConfigurationException; public class HttpClientFactory implements IHttpClientFactory { private static final Logger log = LoggerFactory.getLogger(HttpClientFactory.class); @Autowired(required=true) private IConfiguration basicConfig; 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"; public static final String PROP_CONFIG_CLIENT_AUTH_SSL_KEYSTORE_TYPE = "client.auth.ssl.keystore.type"; // 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"; public enum ClientAuthMode { NONE("none"), PASSWORD("password"), SSL("ssl"); private final String mode; private ClientAuthMode(String mode) { this.mode = mode; } /** * Get the PVP mode * * @return */ public String getMode() { return this.mode; } public static ClientAuthMode fromString(String s) { try { return ClientAuthMode.valueOf(s.toUpperCase()); } catch (IllegalArgumentException|NullPointerException e) { return null; } } @Override public String toString() { return getMode(); } }; public enum KeyStoreType { PKCS12("pkcs12"), JKS("jks"); private final String type; private KeyStoreType (String type) { this.type = type; } /** * Get the PVP mode * * @return */ public String getType() { return this.type; } public static KeyStoreType fromString(String s) { try { return KeyStoreType.valueOf(s.toUpperCase()); } catch (IllegalArgumentException|NullPointerException e) { return null; } } @Override public String toString() { return getType(); } }; private HttpClientBuilder httpClientBuilder = null; /* (non-Javadoc) * @see at.gv.egiz.eaaf.core.impl.utils.IHttpClientFactory#getHttpClient() */ @Override public CloseableHttpClient getHttpClient() { return getHttpClient(true); } @Override public CloseableHttpClient getHttpClient(boolean followRedirects) { RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); if (!followRedirects) redirectStrategy = new RedirectStrategy() { @Override public boolean isRedirected(HttpRequest request, HttpResponse response, HttpContext context) throws ProtocolException { return false; } @Override public HttpUriRequest getRedirect(HttpRequest request, HttpResponse response, HttpContext context) throws ProtocolException { return null; } }; return httpClientBuilder .setRedirectStrategy(redirectStrategy) .build(); } @PostConstruct private void initalize() { //initialize http client log.trace("Initializing HTTP Client-builder ... "); httpClientBuilder = HttpClients.custom(); //set default request configuration final RequestConfig requestConfig = RequestConfig.custom() .setConnectTimeout(Integer.valueOf(basicConfig.getBasicConfiguration( PROP_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_CONNECTION, DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_CONNECTION)) * 1000) .setConnectionRequestTimeout(Integer.valueOf(basicConfig.getBasicConfiguration( PROP_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_REQUEST, DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_REQUEST)) * 1000) .setSocketTimeout(Integer.valueOf(basicConfig.getBasicConfiguration( PROP_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_SOCKET, DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_TIMEOUT_SOCKET)) * 1000) .build(); httpClientBuilder.setDefaultRequestConfig(requestConfig); //set pool connection if required injectConnectionPoolIfRequired(); final ClientAuthMode clientAuthMode = ClientAuthMode.fromString( basicConfig.getBasicConfiguration(PROP_CONFIG_CLIENT_MODE, ClientAuthMode.NONE.getMode())); log.info("Client authentication-mode is set to: {}", clientAuthMode); //inject basic http authentication if required injectBasicAuthenticationIfRequired(clientAuthMode); //inject authentication if required injectSSLContext(clientAuthMode); } private void injectBasicAuthenticationIfRequired(ClientAuthMode clientAuthMode) { if (clientAuthMode.equals(ClientAuthMode.PASSWORD)) { final CredentialsProvider provider = new BasicCredentialsProvider(); final String username = basicConfig.getBasicConfiguration(PROP_CONFIG_CLIENT_AUTH_HTTP_USERNAME); final String password = basicConfig.getBasicConfiguration(PROP_CONFIG_CLIENT_AUTH_HTTP_PASSORD); if (StringUtils.isEmpty(username)) { log.warn("Http basic authentication was activated but NOT username was set!"); } log.trace("Injecting basic authentication with username: {} and password: {}", username, password); final UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(username, password); provider.setCredentials(AuthScope.ANY, credentials); httpClientBuilder.setDefaultCredentialsProvider(provider); log.info("Basic http authentication was injected with username: {}", username); } else { log.trace("Injection of Http Basic authentication was skipped"); } } private SSLContext buildSSLContextWithSSLClientAuthentication() throws KeyManagementException, UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException, EAAFConfigurationException { log.trace("Injecting SSL client-authentication into http client ... "); final KeyStore keystore = getSSLAuthKeyStore(); final String keyPasswordString = basicConfig.getBasicConfiguration(PROP_CONFIG_CLIENT_AUTH_SSL_KEYSTORE_PASSORD); log.trace("Open SSL Client-Auth keystore with password: {}", keyPasswordString); final char[] keyPassword = (keyPasswordString == null) ? StringUtils.EMPTY.toCharArray() : keyPasswordString.toCharArray(); return SSLContexts.custom().loadKeyMaterial(keystore, keyPassword).build(); } private KeyStore getSSLAuthKeyStore() throws EAAFConfigurationException { final KeyStoreType keyStoreType = KeyStoreType.fromString( basicConfig.getBasicConfiguration(PROP_CONFIG_CLIENT_AUTH_SSL_KEYSTORE_TYPE, KeyStoreType.PKCS12.getType())); final String localKeyStorePath = basicConfig.getBasicConfiguration(PROP_CONFIG_CLIENT_AUTH_SSL_KEYSTORE_PATH, StringUtils.EMPTY); final String keyStorePassword = basicConfig.getBasicConfiguration(PROP_CONFIG_CLIENT_AUTH_SSL_KEYSTORE_PASSORD, StringUtils.EMPTY); try { log.debug("Open keyStore with type: {}", keyStoreType); KeyStore clientStore; if (keyStoreType.equals(KeyStoreType.PKCS12)) { clientStore = KeyStore.getInstance("pkcs12"); } else { clientStore = KeyStore.getInstance("JKS"); } log.debug("Read keyStore path: {} from configuration", localKeyStorePath); if (StringUtils.isNotEmpty(localKeyStorePath)) { String absFilePath = FileUtils.makeAbsoluteURL(localKeyStorePath, basicConfig.getConfigurationRootDirectory()); if (absFilePath.startsWith("file:")) { absFilePath = absFilePath.substring("file:".length()); } final File keyStoreFile = new File(absFilePath); log.trace("Load keyStore: {} with password: {}", absFilePath, keyStorePassword); clientStore.load(new FileInputStream(keyStoreFile), keyStorePassword.toCharArray()); return clientStore; } else { log.warn("Path to keyStore for SSL Client-Authentication is empty or null"); throw new EAAFConfigurationException("Path to keyStore for SSL Client-Authentication is empty or null", new Object[] {}); } } catch (final KeyStoreException | NoSuchAlgorithmException | CertificateException | IOException e) { log.warn("Can NOT read keyStore: {} from filesystem", localKeyStorePath, null, e); throw new EAAFConfigurationException("Can NOT read keyStore: {} from filesystem", new Object[] {localKeyStorePath}, e); } } private void injectSSLContext(ClientAuthMode clientAuthMode) { SSLContext sslContext = null; try { if (clientAuthMode.equals(ClientAuthMode.SSL)) { sslContext = buildSSLContextWithSSLClientAuthentication(); } else { log.trace("Initializing default SSL Context ... "); sslContext = SSLContext.getDefault(); } //set hostname verifier HostnameVerifier hostnameVerifier = null; if (basicConfig.getBasicConfigurationBoolean( PROP_CONFIG_CLIENT_HTTP_SSL_HOSTNAMEVERIFIER_TRUSTALL, false)) { hostnameVerifier = new NoopHostnameVerifier(); log.warn("HTTP client-builder deactivates SSL Host-name verification!"); } final LayeredConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext , hostnameVerifier); httpClientBuilder.setSSLSocketFactory(sslSocketFactory ); } catch (final NoSuchAlgorithmException | KeyManagementException | UnrecoverableKeyException | KeyStoreException | EAAFConfigurationException e) { log.warn("HTTP client-builder can NOT initialze SSL-Context", e); } log.info("HTTP client-builder successfuly initialized"); } private void injectConnectionPoolIfRequired() { if (basicConfig.getBasicConfigurationBoolean( PROP_CONFIG_CLIENT_HTTP_CONNECTION_POOL_USE, true)) { final PoolingHttpClientConnectionManager pool = new PoolingHttpClientConnectionManager(); pool.setDefaultMaxPerRoute(Integer.valueOf(basicConfig.getBasicConfiguration( PROP_CONFIG_CLIENT_HTTP_CONNECTION_POOL_MAXPERROUTE, DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_POOL_MAXPERROUTE))); pool.setMaxTotal(Integer.valueOf(basicConfig.getBasicConfiguration( PROP_CONFIG_CLIENT_HTTP_CONNECTION_POOL_MAXTOTAL, DEFAULT_CONFIG_CLIENT_HTTP_CONNECTION_POOL_MAXTOTAL))); httpClientBuilder.setConnectionManager(pool); log.debug("Initalize http-client pool with, maxTotal: {} maxPerRoute: {}", pool.getMaxTotal(), pool.getDefaultMaxPerRoute()); } } }