如何以编程方式在 Java LDAP JNDI LDAP API 中禁用证书主机名验证?

How to programmatically disable certificate hostname verification in Java LDAP JNDI LDAP API?

Java 8u181 引入了一项更改,在使用 Java JNDI LDAP API 连接到 LDAPS (TLS) 服务器时启用证书主机名验证。

参见:https://www.oracle.com/technetwork/java/javase/8u181-relnotes-4479407.html#JDK-8200666

如何禁用此主机名验证,或者更好的是指定自定义 javax.net.ssl.HostnameVerifier class。 Oracle 的文档仅指定 Java 环境 属性 来禁用验证,但没有说明任何方式来完成此问题,这对于不能(或不想)更改能力的环境至关重要bits/switches 的 JVM 运行。

这个问题:How to disable endpoint identification for java 1.8.181 version 问了一个类似的问题,但解决方案是 java 通过命令行更改环境。我在问如何在没有环境切换的情况下以编程方式完成它。

在 Java 中还有其他 questions/answers 关于为其他类型的 SSL 连接禁用主机名验证,但答案不适用于 JNDI LDAP API。

正如@Patrick-Mevzek 所说:不要这样做!

但如果你真的必须这样做,你可以这样做:

您需要一个 SocketFactory,其中包含一个可以忽略任何内容的虚拟 TrustManager。有很多例子可以展示如何创建这样的东西。不幸的是,他们中的大多数(全部?)使用 X509TrustManager 来完成这项工作。这将适用于无效证书,但不会处理错误或丢失的主机名。为此你需要一个```X509ExtendedTrustManager`:

import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.security.GeneralSecurityException;
import java.security.cert.X509Certificate;

import javax.net.SocketFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509ExtendedTrustManager;

/**
 * This Socket factory will accept all certificates and all hostnames
 */
public class NonVerifyingSSLSocketFactory extends SocketFactory {
   private static SocketFactory nonVerifyingSSLSochetFactory;

   static {
      TrustManager [] distrustManager = 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) {

         }

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

         public void checkClientTrusted (X509Certificate [] c, String a) {
         }

         public void checkServerTrusted (X509Certificate [] c, String a) {
         }
      }};

      try {
         SSLContext sc = SSLContext.getInstance ("SSL");
         sc.init (null, distrustManager, new java.security.SecureRandom ());
         nonVerifyingSSLSochetFactory = sc.getSocketFactory ();
      } catch (GeneralSecurityException e) {
         throw new RuntimeException (e);
      }
   }

   /**
    * This method is needed. It is called by the LDAP Context to create the connection
    *
    * @see SocketFactory#getDefault()
    */
   @SuppressWarnings ("unused")
   public static SocketFactory getDefault () {
      return new NonVerifyingSSLSocketFactory ();
   }

   /**
    * @see SocketFactory#createSocket(String, int)
    */
   public Socket createSocket (String arg0, int arg1) throws IOException {
      return nonVerifyingSSLSochetFactory.createSocket (arg0, arg1);
   }

   /**
    * @see SocketFactory#createSocket(java.net.InetAddress, int)
    */
   public Socket createSocket (InetAddress arg0, int arg1) throws IOException {
      return nonVerifyingSSLSochetFactory.createSocket (arg0, arg1);
   }

   /**
    * @see SocketFactory#createSocket(String, int, InetAddress, int)
    */
   public Socket createSocket (String arg0, int arg1, InetAddress arg2, int arg3) throws IOException {
      return nonVerifyingSSLSochetFactory.createSocket (arg0, arg1, arg2, arg3);
   }

   /**
    * @see SocketFactory#createSocket(InetAddress, int, InetAddress, int)
    */
   public Socket createSocket (InetAddress arg0, int arg1, InetAddress arg2,
                               int arg3) throws IOException {
      return nonVerifyingSSLSochetFactory.createSocket (arg0, arg1, arg2, arg3);
   }

}

在您的 InitialLdapContext 环境中使用它来激活它:

env.put ("java.naming.ldap.factory.socket", NonVerifyingSSLSocketFactory.class.getName ());

测试:

  • openjdk 版本“1.8.0_191”
  • oraclejdk 版本“1.8.0_25”(这个版本不需要它,但它无论如何都可以工作并且不会破坏任何东西)

在创建 httpclient 实例之前设置系统 属性

System.setProperty("jdk.internal.httpclient.disableHostnameVerification", "true");