Java SSL: 如何禁用主机名验证

40

标准Java SSL套接字是否有一种属性可以禁用SSL连接的主机名验证?目前我找到的唯一方法是编写一个主机名验证器,该验证器始终返回true。

Weblogic提供了这种可能性,可以使用以下属性禁用主机名验证:

-Dweblogic.security.SSL.ignoreHostnameVerify


1
你的解决方案几乎是我能想到的最干净的,有什么问题吗? - Piskvor left the building
1
好的,您只是想禁用检查并在不更改代码的情况下执行此操作。通常您有很多属性来控制SSL连接,但显然在这种情况下没有... - paweloque
好的,你可以创建一个主机名验证器工厂,它将检查你的自定义属性并返回“always-ok”虚拟验证器(如果设置了),或默认验证器(如果没有)。然而,这并不能真正解决问题,不是吗? - Piskvor left the building
你说得对,这并不能解决我的问题。事实上,我并没有直接使用套接字,而是使用了wls webservice框架,在这个框架中,当涉及到套接字工厂配置和特别是设置自己的主机名验证器时,似乎我不太掌握主动权。 - paweloque
6个回答

38

可以创建自定义Java代理程序,覆盖默认的HostnameVerifier

import javax.net.ssl.*;
import java.lang.instrument.Instrumentation;

public class LenientHostnameVerifierAgent {
    public static void premain(String args, Instrumentation inst) {
        HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
            public boolean verify(String s, SSLSession sslSession) {
                return true;
            }
        });
    }
}

然后只需将 -javaagent:LenientHostnameVerifierAgent.jar 添加到程序的 Java 启动参数中即可。


2
这可能是一个很好的处理方式,但我正在处理Apache的http客户端。我该如何创建一个Java代理来设置SSLSocketFactory.setHostnameVerifier(new AllowAllHostnameVerifier()) - end-user
1
@end-user,就像这个答案中一样。 - Vadzim
2
请记得添加一个清单文件,其中包含 'Premain-Class: LenientHostnameVerifierAgent'。 - thoredge

12

@Nani的答案在Java 1.8u181中不再适用。你仍然需要使用自己的TrustManager,但它需要是X509ExtendedTrustManager而不是X509TrustManager

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.Socket;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509ExtendedTrustManager;

public class Test {

   public static void main (String [] args) throws IOException {
      // This URL has a certificate with a wrong name
      URL url = new URL ("https://wrong.host.badssl.com/");

      try {
         // opening a connection will fail
         url.openConnection ().connect ();
      } catch (SSLHandshakeException e) {
         System.out.println ("Couldn't open connection: " + e.getMessage ());
      }

      // Bypassing the SSL verification to execute our code successfully
      disableSSLVerification ();

      // now we can open the connection
      url.openConnection ().connect ();

      System.out.println ("successfully opened connection to " + url + ": " + ((HttpURLConnection) url.openConnection ()).getResponseCode ());
   }

   // Method used for bypassing SSL verification
   public static void disableSSLVerification () {

      TrustManager [] trustAllCerts = new TrustManager [] {new X509ExtendedTrustManager () {
         @Override
         public void checkClientTrusted (X509Certificate [] chain, String authType, Socket socket) {

         }

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

         }

         @Override
         public void checkClientTrusted (X509Certificate [] chain, String authType, SSLEngine engine) {

         }

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

         }

         @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 sc = null;
      try {
         sc = SSLContext.getInstance ("SSL");
         sc.init (null, trustAllCerts, new java.security.SecureRandom ());
      } catch (KeyManagementException | NoSuchAlgorithmException e) {
         e.printStackTrace ();
      }
      HttpsURLConnection.setDefaultSSLSocketFactory (sc.getSocketFactory ());
   }
}

还需要添加启动参数吗? - kiltek
当你使用我发布的解决方案时,你不需要任何额外的启动参数。你只需要确保在你的程序中设置了Socket工厂即可。 - Markus Heberling
2
警告:此答案中的解决方案似乎在没有任何警告的情况下禁用了所有信任验证(如果您排除代码中的trustAllCerts)。 - Maarten Bodewes

8

标准的Java SSL套接字或SSL中没有主机名验证,因此您无法在该级别设置它。主机名验证是HTTPS(RFC 2818)的一部分:这就是为什么它表现为javax.net.ssl.HostnameVerifier,并应用于HttpsURLConnection的原因。


1
这听起来很合理,但是为什么Weblogic提供了这样一个开关,而在HttpsURLConnection级别上却没有这样的属性呢? - paweloque
1
@lewap 我猜这些问题是反问句?我无法回答关于WebLogic的问题,也无法回答为什么JDK是这样的问题。对我来说,WebLogic的事情看起来像是一个安全漏洞,我不希望它出现在JDK中。 - user207421
5
他们完全不是花言巧语,我正在努力理解SSL工作原理,并了解JDK和Weblogic之间的区别。也许这有其原因。 - paweloque
@lewap 当然有原因。你只是没有问对人或者在正确的地方提问。 - user207421
这将为主机名验证问题提供更多背景信息,该问题与SSL的jdk实现相关:https://tersesystems.com/blog/2014/03/23/fixing-hostname-verification/ - razvanone

5

我在访问RESTful网络服务时也遇到了同样的问题。我使用以下代码来解决这个问题:

public class Test {
    //Bypassing the SSL verification to execute our code successfully 
    static {
        disableSSLVerification();
    }

    public static void main(String[] args) {    
        //Access HTTPS URL and do something    
    }
    //Method used for bypassing SSL verification
    public static void disableSSLVerification() {

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

            public void checkClientTrusted(X509Certificate[] certs, String authType) {
            }

            public void checkServerTrusted(X509Certificate[] certs, String authType) {
            }

        } };

        SSLContext sc = null;
        try {
            sc = SSLContext.getInstance("SSL");
            sc.init(null, trustAllCerts, new java.security.SecureRandom());
        } catch (KeyManagementException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());

        HostnameVerifier allHostsValid = new HostnameVerifier() {
            public boolean verify(String hostname, SSLSession session) {
                return true;
            }
        };      
        HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);           
    }
}

这对我很有帮助。试试看吧!


11
这个不安全的信任所有证书,但它不会绕过主机名验证,所以它并没有回答这个问题。 - user207421
1
请忽略该注释,它会绕过主机名验证器(请参见HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);)。 - Frischling

3

@user207421是正确的,在标准Java SSL套接字或SSL中确实没有主机名验证。
X509ExtendedTrustManager实现了主机名检查逻辑(请参见其javadoc)。要禁用此功能,我们可以将SSLParameters.endpointIdentificationAlgorithm设置为null,就像JDK AbstractAsyncSSLConnection所做的那样:

        if (!disableHostnameVerification)
            sslParameters.setEndpointIdentificationAlgorithm("HTTPS"); // default is null

disableHostnameVerification属性的值读取自属性:jdk.internal.httpclient.disableHostnameVerification

如何修改SSLParameters对象取决于您使用的具体软件。

例如,Spring WebFlux WebClient:

HttpClient httpClient = HttpClient.create()
    .secure(sslContextSpec ->
        sslContextSpec
            .sslContext(sslContext)
            .handlerConfigurator(sslHandler -> {
                SSLEngine engine = sslHandler.engine();
                SSLParameters newSslParameters = engine.getSSLParameters(); // 返回的是一个新对象
                // 参考:https://github.com/AdoptOpenJDK/openjdk-jdk11/blob/master/src/java.net.http/share/classes/jdk/internal/net/http/AbstractAsyncSSLConnection.java#L116
                newSslParameters.setEndpointIdentificationAlgorithm(null);
                engine.setSSLParameters(newSslParameters);
            })
    )
    
WebClient webclient = WebClient.builder()
                .clientConnector(new ReactorClientHttpConnector(httpClient))
                .build();

我注意到,尽管这个解决方案在运行Java 8更新202时工作正常,但在Java 8更新342或345上却无法工作(尽管我没有检查过后续版本)。 - undefined
Java 8更新382也没有起作用。 - undefined

1
如果你正在使用Apache的http-client 4:

最初的回答:

SSLConnectionSocketFactory sslConnectionSocketFactory = 
    new SSLConnectionSocketFactory(sslContext,
             new String[] { "TLSv1.2" }, null, new HostnameVerifier() {
                    public boolean verify(String arg0, SSLSession arg1) {
                            return true;
            }
      });

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