如何在JAX-WS客户端中禁用证书验证?

22
你如何使用javax.xml.ws.Service在JAX-WS客户端中禁用证书验证?
我尝试在SSLSocketFactory中创建了一个完全信任的TrustManager并尝试将其绑定到BindingProvider。
SSLContext sc = SSLContext.getInstance("SSL"); 
sc.init(null, trustAllCerts, new java.security.SecureRandom()); 

Map<String, Object> ctxt = ((BindingProvider) wsport ).getRequestContext(); 
ctxt.put(JAXWSProperties.SSL_SOCKET_FACTORY, sc.getSocketFactory()); 

但是我仍然得到了 Exception: unable to find valid certification path to requested target

在我只使用以下内容时,它可以正常工作

HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); 

还有没有办法让javax.xml.ws.Service使用我创建的HttpsURLConnection


4
背景很重要。为什么你想要故意引入一个重大的不安全因素?如果你不想要安全性,为什么还要使用HTTPS/SSL呢? - user207421
3
您最好将不受信任的证书添加到Java密钥库中。 - artbristol
为什么人们不能假设其他人在尝试绕过SSL验证时知道他们在做什么? - Kimses
4个回答

20

我在这里找到了一个解决方案: http://schrepfler.blogspot.com.br/2009/06/relaxing-ssl-validation-for-jaxws.html

我正在使用该解决方案,在主类的静态块中调用这两个静态方法,像这样:

static {
    SSLUtilities.trustAllHostnames();
    SSLUtilities.trustAllHttpsCertificates();
}
希望这有所帮助。 编辑:正如David J. Liszewski指出的那样,这会破坏来自此JVM的所有连接的SSL/TLS。所以请谨记这一点。

19
好的。为这个 JVM 中的所有连接突破 SSL/TLS。:( - David J. Liszewski

14
请查看Erik Wramner的博客,从这里可以找到真相: http://erikwramner.wordpress.com/2013/03/27/trust-self-signed-ssl-certificates-and-skip-host-name-verification-with-jax-ws

我包含了一个完整的解决方案,其中使用Apache CXF向自签名的SharePoint https服务发出SOAP Web服务请求:

NaiveSSLHelper.java

import org.apache.cxf.configuration.jsse.TLSClientParameters;
import org.apache.cxf.transport.http.HTTPConduit;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import javax.xml.ws.BindingProvider;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Map;

public class NaiveSSLHelper {
  public static void makeWebServiceClientTrustEveryone(
    Object webServicePort) {
    if (webServicePort instanceof BindingProvider) {
      BindingProvider bp = (BindingProvider) webServicePort;
      Map requestContext = bp.getRequestContext();
      requestContext.put(JAXWS_SSL_SOCKET_FACTORY, getTrustingSSLSocketFactory());
      requestContext.put(JAXWS_HOSTNAME_VERIFIER,
        new NaiveHostnameVerifier());
    } else {
      throw new IllegalArgumentException(
        "Web service port "
          + webServicePort.getClass().getName()
          + " does not implement "
          + BindingProvider.class.getName());
    }
  }

  public static SSLSocketFactory getTrustingSSLSocketFactory() {
    return SSLSocketFactoryHolder.INSTANCE;
  }

  private static SSLSocketFactory createSSLSocketFactory() {
    TrustManager[] trustManagers = new TrustManager[] {
      new NaiveTrustManager()
    };
    SSLContext sslContext;
    try {
      sslContext = SSLContext.getInstance("TLS");
      sslContext.init(new KeyManager[0], trustManagers,
        new SecureRandom());
      return sslContext.getSocketFactory();
    } catch (GeneralSecurityException e) {
      return null;
    }
  }

  public static void makeCxfWebServiceClientTrustEveryone(HTTPConduit http) {
    TrustManager[] trustManagers = new TrustManager[]{
      new NaiveTrustManager()
    };
    TLSClientParameters tlsParams = new TLSClientParameters();
    tlsParams.setSecureSocketProtocol("TLS");
    tlsParams.setKeyManagers(new KeyManager[0]);
    tlsParams.setTrustManagers(trustManagers);
    tlsParams.setDisableCNCheck(true);
    http.setTlsClientParameters(tlsParams);
  }

  private interface SSLSocketFactoryHolder {
    SSLSocketFactory INSTANCE = createSSLSocketFactory();
  }

  private static class NaiveHostnameVerifier implements
    HostnameVerifier {
    @Override
    public boolean verify(String hostName,
                          SSLSession session) {
      return true;
    }
  }

  private static class NaiveTrustManager implements
    X509TrustManager {

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

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

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

  private static final java.lang.String JAXWS_HOSTNAME_VERIFIER =
    "com.sun.xml.internal.ws.transport.https.client.hostname.verifier";
  private static final java.lang.String JAXWS_SSL_SOCKET_FACTORY =
    "com.sun.xml.internal.ws.transport.https.client.SSLSocketFactory";
}

SoapTester.java

import crawler.common.sharepoint.stubs.sitedata.ArrayOfSList;
import crawler.common.sharepoint.stubs.sitedata.GetListCollectionResponse;
import crawler.common.sharepoint.stubs.sitedata.SList;
import crawler.common.sharepoint.stubs.sitedata.SiteData;
import crawler.common.sharepoint.stubs.sitedata.SiteDataSoap;
import org.apache.cxf.configuration.security.AuthorizationPolicy;
import org.apache.cxf.endpoint.Client;
import org.apache.cxf.frontend.ClientProxy;
import org.apache.cxf.transport.http.HTTPConduit;
import org.apache.cxf.transport.http.asyncclient.AsyncHTTPConduit;
import org.apache.cxf.transport.http.auth.HttpAuthHeader;
import org.apache.cxf.transport.http.auth.SpnegoAuthSupplier;
import org.apache.cxf.transports.http.configuration.HTTPClientPolicy;
import org.ietf.jgss.GSSName;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.security.auth.login.AppConfigurationEntry;
import javax.security.auth.login.Configuration;
import javax.xml.namespace.QName;
import javax.xml.soap.SOAPMessage;
import javax.xml.ws.BindingProvider;
import javax.xml.ws.Holder;
import javax.xml.ws.Service;
import javax.xml.ws.handler.Handler;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;
import java.io.ByteArrayOutputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * This example will invoke a web service on SharePoint 2013+ with optional kerberos auth.
 */
public class SoapTester {

  private static final Logger LOG = LoggerFactory.getLogger(SoapTester.class);

  public static void main(String[] args) {

    String endpointAddress = args[0];
    String keytabFilePath = args.length > 2 ? args[1] : null;
    String principalName = args.length > 2 ? args[2] : null;
    String servicePrincipalName = args.length > 3 ? args[3] : null;

    if (!endpointAddress.endsWith("/")) {
      endpointAddress += "/";
    }

    endpointAddress += "_vti_bin/SiteData.asmx";

    final String endpointAddressFinal = endpointAddress;

    Service service = Service.create(SiteData.SERVICE);
    SiteDataSoap soap = service.getPort(SiteDataSoap.class);
    NaiveSSLHelper.makeWebServiceClientTrustEveryone(soap);
    BindingProvider bindingProvider = (BindingProvider) soap;
    bindingProvider.getRequestContext().put(AsyncHTTPConduit.USE_ASYNC,
      Boolean.TRUE);
    bindingProvider.getRequestContext().put(
      BindingProvider.ENDPOINT_ADDRESS_PROPERTY, endpointAddress);

    List<Handler> chain = bindingProvider.getBinding().getHandlerChain();
    chain.add(new SOAPHandler<SOAPMessageContext>() {
      @Override
      public boolean handleMessage(SOAPMessageContext context) {
        String endpointAddress = (String) context.get(BindingProvider.ENDPOINT_ADDRESS_PROPERTY);
        SOAPMessage msg = context.getMessage();
        Boolean outbound = (Boolean) context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
        try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
          msg.writeTo(out);
          String str = new String(out.toByteArray());
          LOG.info("Sharepoint xml [" + endpointAddress + "]" + (outbound ? " (Outbound)" : " (Inbound)") + ": " + str);
        } catch (Exception e) {
          LOG.error("Cannot get soap xml from message ", e);
        }
        if (outbound.booleanValue()) {
          try {
            context.getMessage().setProperty(SOAPMessage.CHARACTER_SET_ENCODING, "UTF-8");
          } catch (Exception e) {
            throw new RuntimeException(e);
          }
        }
        return true;
      }

      @Override
      public boolean handleFault(SOAPMessageContext context) {
        return true;
      }

      @Override
      public void close(MessageContext context) {
      }

      @Override
      public Set<QName> getHeaders() {
        return null;
      }
    });
    bindingProvider.getBinding().setHandlerChain(chain);
    Client client = ClientProxy.getClient(bindingProvider);

    client.getEndpoint().put("org.apache.cxf.stax.maxChildElements", System.getProperty("org.apache.cxf.stax.maxChildElements") != null ? System.getProperty("org.apache.cxf.stax.maxChildElements") : "5000000");
    HTTPConduit http = (HTTPConduit) client.getConduit();
    NaiveSSLHelper.makeCxfWebServiceClientTrustEveryone(http);

    AuthorizationPolicy authorization = new AuthorizationPolicy();
    authorization.setAuthorizationType(HttpAuthHeader.AUTH_TYPE_NEGOTIATE);
    http.setAuthorization(authorization);

    SpnegoAuthSupplier authSupplier = new SpnegoAuthSupplier();
    if (servicePrincipalName != null) {
      authSupplier.setServicePrincipalName(servicePrincipalName);
      authSupplier.setServiceNameType(GSSName.NT_HOSTBASED_SERVICE);
    }

    Map<String, String> loginConfig = new HashMap<>();
    loginConfig.put("useKeyTab", "true");
    loginConfig.put("storeKey", "true");
    loginConfig.put("refreshKrb5Config", "true");
    loginConfig.put("keyTab", keytabFilePath);
    loginConfig.put("principal", principalName);
    loginConfig.put("useTicketCache", "true");
    loginConfig.put("debug", String.valueOf(true));
    authSupplier.setLoginConfig(new Configuration() {
      @Override
      public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
        return new AppConfigurationEntry[] {
          new AppConfigurationEntry("com.sun.security.auth.module.Krb5LoginModule",
            AppConfigurationEntry.LoginModuleControlFlag.REQUIRED,
            loginConfig)};
      }
    });
    http.setAuthSupplier(authSupplier);

    HTTPClientPolicy httpClientPolicy = new HTTPClientPolicy();
    httpClientPolicy.setAllowChunking(false);
    httpClientPolicy.setAutoRedirect(true);

    http.setClient(httpClientPolicy);

    Holder<ArrayOfSList> vLists = new Holder<>();
    Holder<Long> getListCollectionResult = new Holder<>();
    soap.getListCollectionAsync(getListCollectionResult, vLists, res -> {
      try {
        GetListCollectionResponse listCollectionResponse = res.get();
        ArrayOfSList arrayOfSList = listCollectionResponse.getVLists();
        LOG.info("Successfully got {} lists from {}", arrayOfSList.getSList().size(), endpointAddressFinal);
        for (SList slist : arrayOfSList.getSList()) {
          LOG.info("Successfully got list {}", slist.getTitle());
        }
        System.exit(0);
      } catch (Exception e) {
        LOG.error("List collection response", e);
      }
    });
  }
}

这是有关于JDK7和glassfish的另一个例子。请注意 Nikolay Smirnov 的评论。我使用 JDK 7 和 glassfish 3.1.2。在这种环境下,如果服务器处理自签名证书,则建议的解决方案效果完美。

// import com.sun.xml.ws.developer.JAXWSProperties;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
import java.util.Map;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import javax.xml.ws.BindingProvider;
import org.apache.cxf.configuration.jsse.TLSClientParameters;
import org.apache.cxf.endpoint.Client;
import org.apache.cxf.frontend.ClientProxy;
import org.apache.cxf.transport.http.HTTPConduit;

/**
 *
 * Usage examples (BindingProvider port):
 * NaiveSSLHelper.makeWebServiceClientTrustEveryone(port); // GlassFish
 * NaiveSSLHelper.makeCxfWebServiceClientTrustEveryone(port); // TomEE
 * 
 * Based on Erik Wramner's example frome here:
 * http://erikwramner.wordpress.com/2013/03/27/trust-self-signed-ssl-certificates-and-skip-host-name-verification-with-jax-ws/
 *
 * I have extended the functionality when Apache CXF is used.
 */
public class NaiveSSLHelper {

    private static final String JAXWS_HOSTNAME_VERIFIER = "com.sun.xml.ws.transport.https.client.hostname.verifier"; // JAXWSProperties.HOSTNAME_VERIFIER;
    private static final String JAXWS_SSL_SOCKET_FACTORY = "com.sun.xml.ws.transport.https.client.SSLSocketFactory"; // JAXWSProperties.SSL_SOCKET_FACTORY;

    // In Glassfish (Metro) environment you can use this function (Erik Wramner's solution)
    public static void makeWebServiceClientTrustEveryone(Object webServicePort) {
        if (webServicePort instanceof BindingProvider) {
            BindingProvider bp = (BindingProvider) webServicePort;
            Map requestContext = bp.getRequestContext();
            requestContext.put(JAXWS_SSL_SOCKET_FACTORY, getTrustingSSLSocketFactory());
            requestContext.put(JAXWS_HOSTNAME_VERIFIER, new NaiveHostnameVerifier());
        } else {
            throw new IllegalArgumentException(
                    "Web service port "
                    + webServicePort.getClass().getName()
                    + " does not implement "
                    + BindingProvider.class.getName());
        }
    }

    // In TomEE (Apache CXF) environment you can use this function (my solution)
    public static void makeCxfWebServiceClientTrustEveryone(Object port) {
        TrustManager[] trustManagers = new TrustManager[]{
            new NaiveTrustManager()
        };
        Client c = ClientProxy.getClient(port);
        HTTPConduit httpConduit = (HTTPConduit) c.getConduit();
        TLSClientParameters tlsParams = new TLSClientParameters();
        tlsParams.setSecureSocketProtocol("SSL");
        tlsParams.setKeyManagers(new KeyManager[0]);
        tlsParams.setTrustManagers(trustManagers);
        tlsParams.setDisableCNCheck(true);
        httpConduit.setTlsClientParameters(tlsParams);
    }

    public static SSLSocketFactory getTrustingSSLSocketFactory() {
        return SSLSocketFactoryHolder.INSTANCE;
    }

    private static SSLSocketFactory createSSLSocketFactory() {
        TrustManager[] trustManagers = new TrustManager[]{
            new NaiveTrustManager()
        };
        SSLContext sslContext;
        try {
            sslContext = SSLContext.getInstance("SSL");
            sslContext.init(new KeyManager[0], trustManagers, new SecureRandom());
            return sslContext.getSocketFactory();
        } catch (GeneralSecurityException e) {
            return null;
        }
    }

    private static interface SSLSocketFactoryHolder {

        public static final SSLSocketFactory INSTANCE = createSSLSocketFactory();
    }

    private static class NaiveHostnameVerifier implements
            HostnameVerifier {

        @Override
        public boolean verify(String hostName,
                SSLSession session) {
            return true;
        }
    }

    private static class NaiveTrustManager implements
            X509TrustManager {

        @Override
        public void checkClientTrusted(java.security.cert.X509Certificate[] certs,
                String authType) throws java.security.cert.CertificateException {
        }

        @Override
        public void checkServerTrusted(java.security.cert.X509Certificate[] certs,
                String authType) throws java.security.cert.CertificateException {
        }

        @Override
        public java.security.cert.X509Certificate[] getAcceptedIssuers() {
            return new java.security.cert.X509Certificate[0];
        }
    }
}

3
比Jose Renato的答案好多了。链接中的解决方案据说并不适用于所有情况,但至少对我来说(使用OpenJdk 1.8和NetBeans 8.2使用的任何JAX-WS实现),它运行良好。您能否将其复制到此处,以防链接页面可能在未来消失? - pvgoran
1
我已根据@pvgoran的建议添加了解决方案本身。我还包括了使用ApacheCXF的情况。 - Miklos Krivan
感谢尼古拉斯编辑和清理我的句子 :) 现在更容易阅读了 :) - Miklos Krivan

4

如果不考虑与其相关的所有安全问题,如果有人仍然希望在JAX-WS客户端中禁用证书验证,则可以按照以下步骤操作。

NB: 这种方法可以让你仅仅禁用该客户端的证书验证,而不会影响所有连接的SSL/TLS。

import java.security.cert.X509Certificate;

import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import org.apache.cxf.configuration.jsse.TLSClientParameters;
import org.apache.cxf.endpoint.Client;
import org.apache.cxf.frontend.ClientProxy;
import org.apache.cxf.jaxws.JaxWsProxyFactoryBean;
import org.apache.cxf.transport.http.HTTPConduit;

/** Custom JAX-WS client factory used to ignore certificate validation */
public class NotSecureClientFactory extends JaxWsProxyFactoryBean {

    @Override
    protected ClientProxy clientClientProxy(Client c) {
        // Create a client factory that does not validate certificate chains
        ClientProxy cp = super.clientClientProxy(c);
        HTTPConduit httpConduit = (HTTPConduit) cp.getClient().getConduit();
        httpConduit.setTlsClientParameters(tlsClientParameters());
        return cp;
    }

    public TLSClientParameters tlsClientParameters() {
        TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
            public X509Certificate[] getAcceptedIssuers() {
                return null;
            }

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

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

        TLSClientParameters tlsClientParameters = new TLSClientParameters();
        tlsClientParameters.setTrustManagers(trustAllCerts);
        return tlsClientParameters;
    }

}

主机名验证器仍然出现错误。 Miklos还针对特定客户端提供了答案。 - jontro
抱歉,我这边没有主机名验证错误。您能详细说明一下吗? - aadiene
设置tlsClientParameters.setDisableCNCheck(true);可以使其工作。这个解决方案要简单得多。谢谢。另外,添加如何使用此工厂将会很有帮助。这是我使用的代码:NotSecureClientFactory factory = new NotSecureClientFactory(); factory.setServiceClass(YourPort.class); factory.setAddress(address); YourPort port = (YourPort) factory.create(); - jontro

2

实际上,CXF自带一个InsecureTrustManager用于测试目的。

对于Spring Boot应用程序,禁用所有CXF客户端的TLS验证非常容易。不用说,在生产环境中绝不能这样做。

import org.apache.cxf.configuration.jsse.TLSClientParameters;
import org.apache.cxf.endpoint.Client;
import org.apache.cxf.frontend.ClientProxy;
import org.apache.cxf.transport.http.HTTPConduit;
import org.apache.cxf.transport.https.InsecureTrustManager;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;

import javax.inject.Inject;
import javax.xml.ws.BindingProvider;
import java.util.Set;

@ConditionalOnProperty(value = "soap.validate-tls.client", havingValue = "true")
@Configuration
class DisableTlsCxfClientConfig {
    @Inject
    DisableTlsCxfClientConfig(Set<BindingProvider> soapClients) {
        var insecureTlsParam = new TLSClientParameters();
        insecureTlsParam.setTrustManagers(InsecureTrustManager.getNoOpX509TrustManagers());
        insecureTlsParam.setDisableCNCheck(true);
        soapClients.stream()
                .map(ClientProxy::getClient)
                .map(Client::getConduit)
                .map(HTTPConduit.class::cast)
                .forEach(c -> c.setTlsClientParameters(insecureTlsParam));
    }
}


大声喊出@addiene,@jontro和@Miklos Krivan的名字,他们提供了所有的组件!非常感谢你们!

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