私は、自己署名証明書を使用する内部 API への RESTful 呼び出しを行う必要がある SpringBoot アプリケーションに取り組んでいます。
UAT と PROD では適切に署名された証明書を使用しているため、この問題は DEV と QA でのみ発生しています。
Windows 10 マシンで Java 8 を使用して開発しています。
私は運がないので以下を試しました:
Windows の信頼できる証明書に証明書を追加
- Chrome から証明書をダウンロードしました (証明書が無効であることを示すアドレス バー)。
- 次に、Windows エクスプローラーで証明書ファイルを右クリックし、[証明書のインストール]を選択して、ウィザードに従いました。
SSL 検証を無視するコードの追加
RestTemplateを作成するときにSSLUtils.buildRestTemplateメソッドを呼び出しました。
package com.company.project.utils.ssl;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.Map.Entry;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.TrustStrategy;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
import com.company.project.beans.ssl.SslBypassConfiguration;
/**
* This class contains several methods for manipulating SSL certificate verification.
*/
public class SSLUtils
{
/* PRIVATE CONSTANTS */
private static Logger logger = LogManager.getLogger(SSLUtils.class);
/* PRIVATE VARIABLES */
private static HostnameVerifier defaultHostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier();
/* PUBLIC METHODS */
/**
* This method will set custom SSL certificate verification which will
* only forgo SSL certificate verification for white-listed hostnames.
*
* @param sslBypassConfiguration
* The {@link SslBypassConfiguration} that contains the details needed.
*
* @return
* The boolean flag to denote if the operation was successful.
*
* @throws NoSuchAlgorithmException
* If no Provider supports aSSLContextSpi implementation for the
* specified protocol.
* @throws KeyManagementException
* If the initialization fails.
*/
public static boolean setCustomSslChecking(final SslBypassConfiguration sslBypassConfiguration)
throws NoSuchAlgorithmException, KeyManagementException
{
// If the SSL bypass is enabled, then keep going.
if (sslBypassConfiguration.isSslVerificationBypassEnabled())
{
// If there are some hostnames to white-list, then keep going.
if ((sslBypassConfiguration.getWhitelistedHostnames() != null) && (sslBypassConfiguration.getWhitelistedHostnames().size() > 0))
{
final StringBuilder sb = new StringBuilder("Hostnames Being White-Listed:\n");
// Loop over all white-listed hostnames and log them.
for (Entry<String, String> whitelistedHostname : sslBypassConfiguration.getWhitelistedHostnames().entrySet())
{
sb.append(whitelistedHostname.getKey())
.append(" (")
.append(whitelistedHostname.getValue())
.append(")");
}
logger.warn(sb.toString());
}
else
{
logger.warn("SSL certificate verification bypass is enabled, but no white-listed hostnames have been specified.");
}
// Create the hostname verifier to be used.
final WhitelistHostnameVerifier whitelistHostnameVerifier = new WhitelistHostnameVerifier(sslBypassConfiguration);
// Create the trust manager to be used.
final X509TrustManager trustManager = new TrustingX509TrustManager();
// Assign the custom hostname verifier and trust manager.
SSLUtils.setCustomSslChecking(whitelistHostnameVerifier, trustManager);
return true;
}
return false;
}
/**
* This method will set custom SSL certificate verification.
*
* @param hostnameVerifier
* The {@link javax.net.ssl.HostnameVerifier} that will be used to verify hostnames.
* @param trustManager
* The {@link X509TrustManager} that will be used to verify certificates.
*
* @throws NoSuchAlgorithmException
* If no Provider supports aSSLContextSpi implementation for the specified protocol.
* @throws KeyManagementException
* If the initialization fails.
*/
public static void setCustomSslChecking(
final javax.net.ssl.HostnameVerifier hostnameVerifier,
final X509TrustManager trustManager)
throws NoSuchAlgorithmException, KeyManagementException
{
// Get an instance of the SSLContent.
final SSLContext sslContent = SSLContext.getInstance("SSL"); // TLS
// Set the state using the specified TrustManager.
sslContent.init(null, new TrustManager[] {trustManager}, null);
// Set the derived SSL socket factory.
HttpsURLConnection.setDefaultSSLSocketFactory(sslContent.getSocketFactory());
// Define the default hostname verifier.
javax.net.ssl.HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier);
}
/**
* This method will set the default SSL certificate verification.
*
* @throws NoSuchAlgorithmException
* If no Provider supports aSSLContextSpi implementation for the specified protocol.
* @throws KeyManagementException
* If the initialization fails.
*/
public static void setDefaultSslChecking()
throws NoSuchAlgorithmException, KeyManagementException
{
// Get an instance of the SSLContent.
final SSLContext sslContent = SSLContext.getInstance("SSL"); // TLS
// Return it to the initial state (discovered by reflection, now hardcoded).
sslContent.init(null, null, null);
// Set the default SSL socket factory.
HttpsURLConnection.setDefaultSSLSocketFactory(sslContent.getSocketFactory());
// Define the default hostname verifier.
javax.net.ssl.HttpsURLConnection.setDefaultHostnameVerifier(SSLUtils.defaultHostnameVerifier);
}
/**
* This method will build a new {@link RestTemplate}.
*
* @param sslBypassConfiguration
* The {@link SslBypassConfiguration}.
*
* @return
* The {@link RestTemplate}.
*
* @throws KeyManagementException
* @throws NoSuchAlgorithmException
* @throws KeyStoreException
*/
public static RestTemplate buildRestTemplate(final SslBypassConfiguration sslBypassConfiguration)
throws KeyManagementException, NoSuchAlgorithmException, KeyStoreException
{
if ((sslBypassConfiguration == null) || (!sslBypassConfiguration.isSslVerificationBypassEnabled()))
{
return new RestTemplate();
}
final TrustStrategy acceptingTrustStrategy = new TrustStrategy()
{
@Override
public boolean isTrusted(final java.security.cert.X509Certificate[] chain, final String authType)
throws java.security.cert.CertificateException
{
return true;
}
};
final HttpClientBuilder httpClientBuilder = HttpClients.custom();
final SSLContext sslContext = org.apache.http.ssl.SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy).build();
final SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext);
httpClientBuilder.setSSLSocketFactory(csf);
httpClientBuilder.setSSLHostnameVerifier(new WhitelistHostnameVerifier(sslBypassConfiguration));
final HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClientBuilder.build());
return new RestTemplate(requestFactory);
}
}
考えられるアイデア
HTTP 接続を受け入れ、それらを外部でホストされている HTTPS エンドポイントにルーティングできるアプリケーションはありますか?
このアプリケーションは、証明書の問題を無視する必要があります。