0

我正在开发一个 SpringBoot 应用程序,该应用程序需要对使用自签名证书的内部 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 端点的应用程序?

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

4

3 回答 3

1

关闭所有 ssl 验证的最简单配置是:

与工厂

    HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();

org.apache.http.conn.ssl.NoopHostnameVerifier使用来自的类信任所有 TLS

`<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.3</version>
</dependency>` 

创建一个新客户端

    SSLContext sslcontext = SSLContexts.custom() .loadTrustMaterial(null, (chain, authType) -> true) .build(); 
    SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, new String[]{"TLSv1"}, null, new NoopHostnameVerifier()); 
    CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();

使用这个新客户端创建一个新的 resttemplate

    requestFactory.setHttpClient(httpClient);
    RestTemplate restTemplate = new RestTemplate(requestFactory);

恢复:

HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();

SSLContext sslcontext = SSLContexts.custom() .loadTrustMaterial(null, (chain, authType) -> true) .build(); 
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, new String[]{"TLSv1"}, null, new NoopHostnameVerifier()); 
CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();


requestFactory.setHttpClient(httpClient);
RestTemplate restTemplate = new RestTemplate(requestFactory);
于 2019-09-14T13:14:06.633 回答
1

RestTemplate跳过https请求证书验证问题:

@Override protected void prepareConnection(HttpURLConnection connection, String httpMethod) {

    try {
        if (!(connection instanceof HttpsURLConnection)) {
            throw new RuntimeException("An instance of HttpsURLConnection is expected");
        }

        HttpsURLConnection httpsConnection = (HttpsURLConnection) connection;

        TrustManager[] trustAllCerts = new TrustManager[]{
                new X509TrustManager() {
                    @Override
                    public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                        return null;
                    }
                    @Override
                    public void checkClientTrusted(X509Certificate[] certs, String authType) {
                    }
                    @Override
                    public void checkServerTrusted(X509Certificate[] certs, String authType) {
                    }

                }
        };
        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
        httpsConnection.setSSLSocketFactory(new MyCustomSSLSocketFactory(sslContext.getSocketFactory()));

        httpsConnection.setHostnameVerifier(new HostnameVerifier() {
            @Override
            public boolean verify(String s, SSLSession sslSession) {
                return true;
            }
        });

        super.prepareConnection(httpsConnection, httpMethod);
    } catch (Exception e) {
        e.printStackTrace();
    }
}


// SSLSocketFactory用于创建 SSLSockets
private static class MyCustomSSLSocketFactory extends SSLSocketFactory {

    private final SSLSocketFactory delegate;

    public MyCustomSSLSocketFactory(SSLSocketFactory delegate) {
        this.delegate = delegate;
    }

    // 返回默认启用的密码套件。除非一个列表启用,对SSL连接的握手会使用这些密码套件。
    // 这些默认的服务的最低质量要求保密保护和服务器身份验证
    @Override
    public String[] getDefaultCipherSuites() {
        return delegate.getDefaultCipherSuites();
    }

    // 返回的密码套件可用于SSL连接启用的名字
    @Override
    public String[] getSupportedCipherSuites() {
        return delegate.getSupportedCipherSuites();
    }


    @Override
    public Socket createSocket(final Socket socket, final String host, final int port, 
            final boolean autoClose) throws IOException {
         Socket underlyingSocket = null;
        try {
            underlyingSocket = delegate.createSocket(socket, host, port, autoClose);
        } catch (java.io.IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return overrideProtocol(underlyingSocket);
    }


    @Override
    public Socket createSocket(final String host, final int port) throws IOException {
        Socket underlyingSocket = null;
        try {
            underlyingSocket = delegate.createSocket(host, port);
        } catch (java.io.IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return overrideProtocol(underlyingSocket);
    }

    @Override
    public Socket createSocket(final String host, final int port, final InetAddress localAddress, 
            final int localPort) throws
            IOException {
        Socket underlyingSocket = null;
        try {
            underlyingSocket = delegate.createSocket(host, port, localAddress, localPort);
        } catch (java.io.IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return overrideProtocol(underlyingSocket);
    }

    @Override
    public Socket createSocket(final InetAddress host, final int port) throws IOException {
        Socket underlyingSocket = null;
        try {
            underlyingSocket = delegate.createSocket(host, port);
        } catch (java.io.IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return overrideProtocol(underlyingSocket);
    }

    @Override
    public Socket createSocket(final InetAddress host, final int port, final InetAddress localAddress, 
            final int localPort) throws
            IOException {
        Socket underlyingSocket = null;
        try {
            underlyingSocket = delegate.createSocket(host, port, localAddress, localPort);
        } catch (java.io.IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return overrideProtocol(underlyingSocket);
    }

    private Socket overrideProtocol(final Socket socket) {
        if (!(socket instanceof SSLSocket)) {
            throw new RuntimeException("An instance of SSLSocket is expected");
        }
        ((SSLSocket) socket).setEnabledProtocols(new String[]{"TLSv1"});
        return socket;
    }
}
于 2019-11-08T01:54:57.457 回答
0

我找到了一个解决方案来忽略 RestTemplates 的 SSL 验证。不过,我不能相信,我在另一个问题上找到了答案:其他答案

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

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;
    }

}

白名单主机名验证程序

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;
    }

}

SslBypass配置

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;
        }

    }

}
于 2019-03-13T20:05:30.330 回答