使用RestTemplate忽略Spring Boot中的自签名证书

问题描述 投票:0回答:1

我正在开发一个Spring Boot应用程序,它需要对使用自签名证书的内部API进行RESTful调用。

我只在DEV和QA中遇到此问题,因为在UAT和PROD中他们使用的是正确签名的证书。

我正在开发Windows 10计算机并使用Java 8。


我已经尝试过以下没有运气了:

为Windows受信任证书添加了证书

  1. 我从Chrome下载了证书(在地址栏中显示证书无效)。
  2. 然后,在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端点?

此应用程序必须忽略任何证书问题。

java spring-boot certificate resttemplate self-signed-certificate
1个回答
0
投票

我找到了忽略RestTemplates的SSL验证的解决方案。我不能接受这个功劳,我在另一个问题上找到答案:other answer

这是我的代码,如果其他人可以使用它。

SSLUtils

package com.company.project.utils.ssl;

import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;

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.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.client.RestTemplate;

import com.company.project.beans.ssl.SslBypassConfiguration;

/**
 * This class contains several methods for manipulating SSL certificate verification.
 * 
 * @author Matthew Weiler
 */
public class SSLUtils
{

    /* PRIVATE CONSTANTS */
    private static Logger logger = LogManager.getLogger(SSLUtils.class);

    /* PUBLIC CONSTANTS */
    public static final HostnameVerifier DEFAULT_HOSTNAMEVERIFIER = HttpsURLConnection.getDefaultHostnameVerifier();

    /* PUBLIC METHODS */
    /**
     * This method will build a {@link RestTemplate}.
     * 
     * @param sslBypassConfiguration
     * The {@link SslBypassConfiguration}.
     * @param username
     * The username to be used when establishing a connection.
     * @param password
     * The password to be used when establishing a connection.
     * 
     * @return
     * The {@link RestTemplate}.
     * 
     * @throws KeyManagementException
     * @throws NoSuchAlgorithmException
     */
    public static RestTemplate buildRestTemplate(final SslBypassConfiguration sslBypassConfiguration, final String username, final String password)
            throws KeyManagementException, NoSuchAlgorithmException
    {
        final RestTemplate restTemplate = new RestTemplate(SSLUtils.createSecureTransport(sslBypassConfiguration, username, password));

        restTemplate.getMessageConverters().add(new StringHttpMessageConverter());

        return restTemplate;
    }

    /* PROTECTED METHODS */
    /**
     * This method will build a {@link ClientHttpRequestFactory}.
     * 
     * @param sslBypassConfiguration
     * The {@link SslBypassConfiguration}.
     * @param username
     * The username to be used when establishing a connection.
     * @param password
     * The password to be used when establishing a connection.
     * 
     * @return
     * The {@link ClientHttpRequestFactory}.
     * 
     * @throws KeyManagementException
     * @throws NoSuchAlgorithmException
     */
    protected static ClientHttpRequestFactory createSecureTransport(final SslBypassConfiguration sslBypassConfiguration, final String username, final String password) throws KeyManagementException, NoSuchAlgorithmException
    {
        final HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();

        if (sslBypassConfiguration.isSslVerificationBypassEnabled())
        {
            httpClientBuilder.setSSLHostnameVerifier(new WhitelistHostnameVerifier(sslBypassConfiguration));
        }

        httpClientBuilder.setSSLContext(SSLUtils.createContext(sslBypassConfiguration));

        // If credentials are supplied, apply them to the http-client-builder.
        if (((username != null) && (username.trim().length() > 0)) || ((password != null) && (password.trim().length() > 0)))
        {
            final UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(username, password);
            final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();

            credentialsProvider.setCredentials(new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT, AuthScope.ANY_REALM), credentials);

            httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
        }

        return new HttpComponentsClientHttpRequestFactory(httpClientBuilder.build());
    }

    /**
     * This method will build a {@link SSLContext}.
     * 
     * @param sslBypassConfiguration
     * The {@link SslBypassConfiguration}.
     * 
     * @return
     * The {@link SSLContext}.
     * 
     * @throws NoSuchAlgorithmException
     * @throws KeyManagementException
     */
    protected static SSLContext createContext(final SslBypassConfiguration sslBypassConfiguration)
            throws NoSuchAlgorithmException, KeyManagementException
    {
        final SSLContext sslContext = SSLContext.getInstance("SSL");

        if (sslBypassConfiguration.isSslVerificationBypassEnabled())
        {
            final TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager()
            {
                public java.security.cert.X509Certificate[] getAcceptedIssuers()
                {
                    return null;
                }

                public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType)
                {

                }

                public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType)
                {

                }
            }};

            sslContext.init(null, trustAllCerts, null);
        }
        else
        {
            sslContext.init(null, null, null);
        }

        SSLContext.setDefault(sslContext);
        HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());

        if (sslBypassConfiguration.isSslVerificationBypassEnabled())
        {
            HttpsURLConnection.setDefaultHostnameVerifier(new WhitelistHostnameVerifier(sslBypassConfiguration));
        }
        else
        {
            HttpsURLConnection.setDefaultHostnameVerifier(SSLUtils.DEFAULT_HOSTNAMEVERIFIER);
        }

        return sslContext;
    }

}

白名单HostnameVerifier

package com.company.project.utils.ssl;

import java.util.Map.Entry;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import com.company.project.beans.ssl.SslBypassConfiguration;

/**
 * This {@link HostnameVerifier} will ignore the SSL validation for hostnames that are on the white-list.
 * 
 * @author Matthew Weiler
 */
public class WhitelistHostnameVerifier implements HostnameVerifier
{

    /* PRIVATE CONSTANTS */
    private static Logger logger = LogManager.getLogger(WhitelistHostnameVerifier.class);

    /* PRIVATE VARIABLES */
    private final SslBypassConfiguration sslBypassConfiguration;

    /* CONSTRUCTORS */
    /**
     * This will create a new {@link WhitelistHostnameVerifier}.
     * 
     * @param sslBypassConfiguration
     * The {@link SslBypassConfiguration} to be used.
     */
    public WhitelistHostnameVerifier(
            final SslBypassConfiguration sslBypassConfiguration)
    {
        this.sslBypassConfiguration = sslBypassConfiguration;
    }

    /* PUBLIC METHODS */
    @Override
    public boolean verify(final String host, final SSLSession session)
    {
        if (this.sslBypassConfiguration.isSslVerificationBypassEnabled())
        {
            if ((this.sslBypassConfiguration.getWhitelistedHostnames() != null) && (this.sslBypassConfiguration.getWhitelistedHostnames().size() > 0))
            {
                for (Entry<String, String> whitelistEntry : this.sslBypassConfiguration.getWhitelistedHostnames().entrySet())
                {
                    if (whitelistEntry.getValue().equals(host))
                    {
                        logger.info("Not performing validation on SSL connection for: " + whitelistEntry.getKey() + " (" + whitelistEntry.getValue() + ")");

                        return true;
                    }
                }
            }
        }

        // important: use default verifier for all other hosts
        return SSLUtils.DEFAULT_HOSTNAMEVERIFIER.verify(host, session);
    }

    /**
     * This method will get the {@link SslBypassConfiguration}.
     * 
     * @return
     * The {@link SslBypassConfiguration}.
     */
    public SslBypassConfiguration getSslConfiguration()
    {
        return this.sslBypassConfiguration;
    }

}

Ssl旁路配置

package com.company.project.mobile.beans.ssl;

import java.util.HashMap;
import java.util.Map;

import javax.net.ssl.HostnameVerifier;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

import com.company.project.utils.StringUtils;

/**
 * This {@link HostnameVerifier} will ignore the SSL validation for hostnames that are on the white-list.
 * 
 * @author Matthew Weiler
 */
@Component
public class SslBypassConfiguration
{

    /* PRIVATE VARIABLES */
    @Value("${application.ssl.bypass.enabled}") 
    private String sslVerificationBypassEnabled;
    @Autowired
    private WhitelistedConfig whitelistedConfig;

    /* PUBLIC METHODS */
    /**
     * This method will get the boolean flag to denote if the SSL handshake bypass is enabled.
     * 
     * @return
     * The boolean flag to denote if the SSL handshake bypass is enabled.
     */
    public boolean isSslVerificationBypassEnabled()
    {
        return StringUtils.isTrue(this.sslVerificationBypassEnabled, false);
    }

    /**
     * This method will get the {@link Map} of white-listed hostnames and their logical names as keys.
     * 
     * @return
     * The {@link Map} of white-listed hostnames and their logical names as keys.
     */
    public Map<String, String> getWhitelistedHostnames()
    {
        return this.whitelistedConfig.getHostnames();
    }

    /* PUBLIC CLASSES */
    @Configuration
    @EnableConfigurationProperties
    @ConfigurationProperties(prefix = "application.ssl.bypass.whitelisted")
    public class WhitelistedConfig
    {

        /* PRIVATE VARIABLES */
        private Map<String, String> hostnames = new HashMap<String, String>();

        /* PUBLIC METHODS */
        /**
         * This method will get the {@link Map} of white-listed hostnames and their logical names as keys.
         * 
         * @return
         * The {@link Map} of white-listed hostnames and their logical names as keys.
         */
        public Map<String, String> getHostnames()
        {
            return this.hostnames;
        }

    }

}
© www.soinside.com 2019 - 2024. All rights reserved.