如何在Feign中忽略SSL证书信任错误?

23

如何在feign客户端中实现curl -k

我知道我可以做到这一点。只是想知道是否有一种忽略或禁用的方法。

new Client.Default(SSLSocketFactory sslContextFactory, HostnameVerifier hostnameVerifier)
7个回答

28

免责声明

由于存在多个非常重要的理由,您不应该真正这样做。 修复SSL问题的最简单方法是遵循SSL最佳实践并使用有效的证书。在线上有许多优秀的项目,例如https://letsencrypt.org/,如果主机是公开可访问的(如果它具有可验证的真实主机名),甚至可以免费获得出色的安全性。

自行承担风险。确保您了解正在违反很多最佳实践,并且只有在您知道这一点时才使用此功能。

如果您使用此示例代码导致某种重大问题,您将承担责任。

真实情况

我也遇到了同样的问题,处理我想从spring-boot应用程序调用的内部(公开不可访问)服务,我使用以下代码解决了这个问题。

简要概述

许多人会告诉您,您可以接受所有证书,将特定证书硬编码到其中,或者执行其他操作。实际上,您可以仅允许通过代码路径的某些可信主机,这是我在此尝试的额外安全层。

在此示例代码中,您可以将多个主机传递给类,并且它应仅允许使用无效证书向这些主机发出请求,而其他所有内容都将按照正常的命令链进行。

这实际上不是生产级别的代码,但希望您会在其中获得一些用处。

够了教育,接下来可能最让您感兴趣的是。

代码

这是为Java 8和spring-boot使用的。

配置

@Configuration
    public class FeignClientConfiguration {

    @Bean
    public Client client() throws NoSuchAlgorithmException, 
        KeyManagementException {

        return new Client.Default(
            new NaiveSSLSocketFactory("your.host.here"),
            new NaiveHostnameVerifier("your.host.here"));
    }
}

NaiveHostnameVerifier

public class NaiveHostnameVerifier implements HostnameVerifier {
    private final Set<String> naivelyTrustedHostnames;

    private final HostnameVerifier hostnameVerifier =
        HttpsURLConnection.getDefaultHostnameVerifier();

    public NaiveHostnameVerifier(String ... naivelyTrustedHostnames) {
        this.naivelyTrustedHostnames =
                Collections.unmodifiableSet(
                    new HashSet<>(Arrays.asList(naivelyTrustedHostnames)));
    }

    @Override
    public boolean verify(String hostname, SSLSession session) {
        return naivelyTrustedHostnames.contains(hostname) ||
                hostnameVerifier.verify(hostname, session);
    }
}
NaiveSSLSocketFactory
public class NaiveSSLSocketFactory extends SSLSocketFactory {
    private final SSLSocketFactory sslSocketFactory = 
                    (SSLSocketFactory) SSLSocketFactory.getDefault();

    private final SSLContext alwaysAllowSslContext;
    private final Set<String> naivelyTrustedHostnames;

    public NaiveSSLSocketFactory(String ... naivelyTrustedHostnames) 
        throws NoSuchAlgorithmException, KeyManagementException {

        this.naivelyTrustedHostnames = 
                Collections.unmodifiableSet(
                    new HashSet<>(Arrays.asList(naivelyTrustedHostnames)));

        alwaysAllowSslContext = SSLContext.getInstance("TLS");
        TrustManager tm = new X509TrustManager() {

            @Override
            public void checkClientTrusted(X509Certificate[] chain, String authType) 
                throws CertificateException {}

            @Override
            public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {}

            @Override
            public X509Certificate[] getAcceptedIssuers() {
                    return null;
            }
        };

        alwaysAllowSslContext.init(null, new TrustManager[] { tm }, null);
    }

    @Override
    public String[] getDefaultCipherSuites() {
        return sslSocketFactory.getDefaultCipherSuites();
    }

    @Override
    public String[] getSupportedCipherSuites() {
        return sslSocketFactory.getSupportedCipherSuites();
    }

    @Override
    public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException {
        return (naivelyTrustedHostnames.contains(host)) 
            ? alwaysAllowSslContext.getSocketFactory().createSocket(socket, host, port, autoClose) 
            : sslSocketFactory.createSocket(socket, host, port, autoClose);
    }

    @Override
    public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
        return (naivelyTrustedHostnames.contains(host)) 
            ? alwaysAllowSslContext.getSocketFactory().createSocket(host, port) 
            : sslSocketFactory.createSocket(host, port);
    }

    @Override
    public Socket createSocket(String host, int port, InetAddress localAddress, int localPort) throws IOException, UnknownHostException {
        return (naivelyTrustedHostnames.contains(host)) 
            ? alwaysAllowSslContext.getSocketFactory().createSocket(host, port, localAddress, localPort) 
            : sslSocketFactory.createSocket(host, port, localAddress, localPort);
    }

    @Override
    public Socket createSocket(InetAddress host, int port) throws IOException {
        return (naivelyTrustedHostnames.contains(host.getHostName())) 
            ? alwaysAllowSslContext.getSocketFactory().createSocket(host, port) 
            : sslSocketFactory.createSocket(host, port);
    }

    @Override
    public Socket createSocket(InetAddress host, int port, InetAddress localHost, int localPort) throws IOException {
        return (naivelyTrustedHostnames.contains(host.getHostName())) 
            ? alwaysAllowSslContext.getSocketFactory().createSocket(host, port, localHost, localPort) 
            : sslSocketFactory.createSocket(host, port, localHost, localPort);
    }
}

参考资料

我在这篇答案中借鉴了很多内容:

使用HttpClient通过HTTPS信任所有证书


1
感谢您提供详细的答案。 - Bee

22

使用 Spring Cloud Netflix >= 1.4.4.RELEASE 时,还可以执行以下操作:

添加 okhttp 客户端的maven依赖:

    <dependency>
        <groupId>io.github.openfeign</groupId>
        <artifactId>feign-okhttp</artifactId>
    </dependency>

并添加以下属性:

feign.httpclient.disableSslValidation=true
feign.httpclient.enabled=false
feign.okhttp.enabled=true

Reference: https://github.com/spring-cloud/spring-cloud-netflix/issues/2729


这个解决方案会导致任何安全漏洞吗? - kashi viswanath
2
disableSslValidation 也适用于 Apache HttpClient。请参见下面的答案。 - Bertolt
@kashiviswanath 嗯...你可以问一下,禁用SSL证书验证是否会导致任何安全漏洞。这些选项正在执行此操作 ;)在我看来,接受委托证书应该根据使用情况进行。 - Kristoff

10

在当前版本的spring-cloud-starter-openfeign中,禁用主机名验证的方法如下:

如果使用Apache HttpClient:

在application.yml文件中设置disable-ssl-validation属性。

feign.httpclient.disable-ssl-validation: true

在pom.xml中添加feign-httpclient依赖。
<dependency>
  <groupId>io.github.openfeign</groupId>
  <artifactId>feign-httpclient</artifactId>
</dependency>

如果您喜欢使用okhttp,您需要通过另一个应用程序属性启用okhttp,并添加feign-okhttp依赖项:

feign.httpclient.disableSslValidation=true
feign.httpclient.enabled=false
feign.okhttp.enabled=true

<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-okhttp</artifactId>
</dependency>

对于httpclient5 (hc5),disable-ssl-validation属性可悲地无法关闭主机名验证(尚未?),请参考以下链接: https://github.com/spring-cloud/spring-cloud-openfeign/issues/625

应用程序属性,用于启用hc5。

feign.httpclient.disableSslValidation=true
feign.httpclient.hc5.enabled=true

添加Maven依赖
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-hc5</artifactId>
</dependency>

注意:对我而言最棘手的部分是我忘了将feign-httpclient作为依赖项添加。在这种情况下,将使用默认的feign客户端并启用主机名验证。


7

通过Feign配置覆盖

@Bean
public Client feignClient()
{
    Client trustSSLSockets = new Client.Default(getSSLSocketFactory(), new NoopHostnameVerifier());
    return trustSSLSockets;
}


private SSLSocketFactory getSSLSocketFactory() {
    try {
        TrustStrategy acceptingTrustStrategy = new TrustStrategy() {
            @Override
            public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                return true;
            }
        };

        SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy).build();
        return sslContext.getSocketFactory();
    } catch (Exception exception) {
    }
    return null;
}

5

对我来说,feign.httpclient.disableSslValidation = true没有起作用。

通过以下代码在配置中创建客户端bean可以工作:

import feign.Client;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.ssl.SSLContexts;
import org.springframework.context.annotation.Bean;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;

public class ClientConfiguration {

    @Bean
    public Client feignClient() {
        return new Client.Default(getSSLSocketFactory(), new NoopHostnameVerifier());
    }

    private SSLSocketFactory getSSLSocketFactory() {
        try {
            SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(null, new TrustSelfSignedStrategy()).build();
            return sslContext.getSocketFactory();
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }

}

pom.xml可能需要添加依赖项:

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

我找不到大部分的导入,你能详细说明一下你的示例并添加所有需要的部分吗? - BiAiB
1
你好,感谢更新,我之前没有安装Apache Commons依赖。但是这对我没有用,我仍然遇到了SSL验证错误。最终,我将证书添加到了JVM信任库中。 - BiAiB

2

在application.yaml文件中添加以下属性以禁用SSL验证。

feign.httpclient.disableSslValidation=true

或者作为VM参数

-Dfeign.httpclient.disableSslValidation=true


1
将以下类添加到您的存储库中,并使用此类作为配置。
这段代码对我有效:
@Configuration
public class SSLSocketClient {


@Bean
public Client feignClient() {
    return new Client.Default(getSSLSocketFactory(),getHostnameVerifier());
}

//SSLSocketFactory
// Install the all-trusting trust manager
public static SSLSocketFactory getSSLSocketFactory() {
    try {
        SSLContext sslContext = SSLContext.getInstance("SSL");
        sslContext.init(null, getTrustManager(), new SecureRandom());
        return sslContext.getSocketFactory();
    }
    catch (Exception e) {
        throw new RuntimeException(e);
    }
}

//TrustManager
// trust manager that does not validate certificate chains
private static TrustManager[] getTrustManager() {
    TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType) {

        }

        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType) {

        }

        @Override
        public X509Certificate[] getAcceptedIssuers()
        {
            return new X509Certificate[]{};
        }
    }};
    return trustAllCerts;
}

//HostnameVerifier
public static HostnameVerifier getHostnameVerifier() {
    HostnameVerifier hostnameVerifier = new HostnameVerifier() {
        @Override
        public boolean verify(String s, SSLSession sslSession)
        {
            return true;
        }
    };
    return hostnameVerifier;
}}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接