忽略使用Jersey Client时的自签名SSL证书

73
我正在使用Jersey Client库来对在jboss上运行的rest服务运行测试。 我已经在服务器上(在localhost上运行)成功设置了https,使用自签名证书。
然而,每当我使用https url运行我的测试时,我会收到以下错误:
com.sun.jersey.api.client.ClientHandlerException: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
    at com.sun.jersey.client.urlconnection.URLConnectionClientHandler.handle(URLConnectionClientHandler.java:131)
    at com.sun.jersey.api.client.Client.handle(Client.java:629)
    at com.sun.jersey.oauth.client.OAuthClientFilter.handle(OAuthClientFilter.java:137)
    at com.sun.jersey.api.client.WebResource.handle(WebResource.java:601)
    at com.sun.jersey.api.client.WebResource.access$200(WebResource.java:74)
    at com.sun.jersey.api.client.WebResource$Builder.get(WebResource.java:459)
    at test.helper.Helper.sendSignedRequest(Helper.java:174)
    ... And so on
我知道这是因为我的自签名证书不在Java密钥库中。是否有任何方法可以使Client不检查SSL证书的有效性,而只是无论如何都使用它?
该代码仅针对测试服务器运行,因此我不想为每次设置新测试服务器时添加新的受信任证书而麻烦。
以下是进行调用的代码:
OAuthParameters params = new OAuthParameters();

// baseline OAuth parameters for access to resource
params.signatureMethod(props.getProperty("signature_method"));
params.consumerKey(props.getProperty("consumer_key"));
params.setToken(props.getProperty("token"));
params.setVersion("1.0");
params.nonce();

// OAuth secrets to access resource
OAuthSecrets secrets = new OAuthSecrets();
secrets.consumerSecret(props.getProperty("consumer_secret"));
secrets.setTokenSecret(props.getProperty("token_secret"));

// Jersey client to make REST calls to token services
Client client = Client.create();

// OAuth test server resource
WebResource resource = client.resource(props.getProperty("url"));

// if parameters and secrets remain static, filter cab be added to each web resource
OAuthClientFilter filter = new OAuthClientFilter(client.getProviders(), params, secrets);

// filter added at the web resource level
resource.addFilter(filter);
WebResource.Builder wbr = resource.getRequestBuilder().accept(props.getProperty("accept"));

return wbr.get(ClientResponse.class);

非常感谢您的帮助。


这个回答解决了你的问题吗?Java客户端证书通过HTTPS/SSL - Matej J
10个回答

102

在经过一番搜索和查看一些旧的stackoverflow问题后,我在之前的一个SO问题中找到了一个解决方案:

这是我最终使用的代码。

// Create a trust manager that does not validate certificate chains
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){}
}};

// Install the all-trusting trust manager
try {
    SSLContext sc = SSLContext.getInstance("TLS");
    sc.init(null, trustAllCerts, new SecureRandom());
    HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
} catch (Exception e) {
    ;
}

我没有指定KeyStore。我使用了上面的代码,没有任何其他VM参数。如果我相信代码工作是因为它不检查它是否由受信任的签名者签名,它只检查它是否格式正确。 - Chris Salij
9
为使其正常工作,我不得不将 public X509Certificate[] getAcceptedIssuers(){return null;} 替换为 public X509Certificate[] getAcceptedIssuers(){return new X509Certificate[0];}。请注意,修改后的代码与原始意图相同。 - Frederic Leitenberger
6
这个答案将Java中的所有 HTTPS连接设置为忽略SSL证书... 这可能不是你想要的,因为可以像@eitan和其他人所示的那样仅针对特定客户端执行此操作。 - Renato
1
此答案也不符合“TrustManager.getAcceptedIssuers()”的契约。请勿使用。 - user207421
1
不支持Java 11。 - mariq vlahova
显示剩余2条评论

82

对于Jersey 2.* (在2.7上测试通过)和Java 8:

import java.security.cert.CertificateException; 
import java.security.cert.X509Certificate; 
import javax.net.ssl.SSLContext; 
import javax.net.ssl.TrustManager; 
import javax.net.ssl.X509TrustManager; 

public static Client ignoreSSLClient() throws Exception {

    SSLContext sslcontext = SSLContext.getInstance("TLS");

    sslcontext.init(null, new TrustManager[]{new X509TrustManager() {
        public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {}
        public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {}
        public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; }
    }}, new java.security.SecureRandom());

    return ClientBuilder.newBuilder()
                        .sslContext(sslcontext)
                        .hostnameVerifier((s1, s2) -> true)
                        .build();
}

4
这个导入包是来自哪里?是Java还是javax? - Dejell
2
import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; - omni
我发现主机名验证是可选的。这可能是因为TLS不需要进行此类检查,与例如https相反。 - lcfd

12
我遇到了同样的问题,并且不想将其全局设定,所以我使用了与上述相同的TrustManager和SSLContext代码,只是将客户端创建时使用了特殊属性。
 ClientConfig config = new DefaultClientConfig();
 config.getProperties().put(HTTPSProperties.PROPERTY_HTTPS_PROPERTIES, new HTTPSProperties(
     new HostnameVerifier() {
         @Override
         public boolean verify( String s, SSLSession sslSession ) {
             // whatever your matching policy states
         }
     }
 ));
 Client client = Client.create(config);

1
移除了arg参数,让HTTPSProperties使用这个构造函数 https://jersey.java.net/nonav/apidocs/1.12/jersey/com/sun/jersey/client/urlconnection/HTTPSProperties.html#HTTPSProperties(javax.net.ssl.HostnameVerifier) - Ransom Briggs

9

由于我是 stackoverflow 上的新用户,声望还不够高,不能在别人的答案下发表评论,因此我按照 Chris Salij 建议的方法进行了一些修改,并且对我有效。

SSLContext ctx = null;
TrustManager[] trustAllCerts = new X509TrustManager[]{new X509TrustManager(){
    public X509Certificate[] getAcceptedIssuers(){return null;}
    public void checkClientTrusted(X509Certificate[] certs, String authType){}
    public void checkServerTrusted(X509Certificate[] certs, String authType){}
}};
try {
    ctx = SSLContext.getInstance("SSL");
    ctx.init(null, trustAllCerts, null);
} catch (NoSuchAlgorithmException | KeyManagementException e) {
    LOGGER.info("Error loading ssl context {}", e.getMessage());
}

SSLContext.setDefault(ctx);

1
请问您能否发布您的导入(import)代码? - thermz
https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient/4.5.13 - mariq vlahova

8
这段代码只会在测试服务器上运行,因此我不想费力添加新的可信证书,每次我们设置新的测试服务器时都需要这样做。
这是那种最终会进入生产环境的代码(如果不是你,阅读此问题的其他人将复制并粘贴建议的不安全信任管理器到他们的应用程序中)。当你有截止日期时,很容易忘记删除这种代码,因为它不会显示为问题。
如果您担心每次拥有测试服务器时都必须添加新证书,请创建自己的小型CA,使用该CA为测试服务器颁发所有证书,并将此CA证书导入客户端信任存储库。(即使您在本地环境中不处理诸如在线证书吊销之类的事情,这肯定比使用允许任何内容通过的信任管理器要好。)
有一些工具可以帮助您完成此操作,例如TinyCAXCA

虽然原始的TinyCA项目似乎已经停滞不前,但目前它由Debian维护。 - hlovdal

7

如果你正在使用 Jersey 2.x 版本且没有使用 lambda 表达式,可以使用以下代码:

import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;

public static Client getUnsecureClient() throws Exception 
{
    SSLContext sslcontext = SSLContext.getInstance("TLS");
    sslcontext.init(null, new TrustManager[]{new X509TrustManager() 
    {
            public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException{}
            public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException{}
            public X509Certificate[] getAcceptedIssuers()
            {
                return new X509Certificate[0];
            }

    }}, new java.security.SecureRandom());


    HostnameVerifier allowAll = new HostnameVerifier() 
    {
        @Override
        public boolean verify(String hostname, SSLSession session) {
            return true;
        }
    };

    return ClientBuilder.newBuilder().sslContext(sslcontext).hostnameVerifier(allowAll).build();
}

测试环境为JRE 1.7,使用jersey-client 2.11进行测试。


2

使用lambda进一步简化: Client client = ClientBuilder.newBuilder() .hostnameVerifier((hostname, session) -> true) .build(); - Ramsharan

1
我注意到当使用带有池管理器的Apache http客户端配置时,接受的答案不起作用。
在这种情况下,似乎会默默忽略ClientConfig.sslContext和ClientConfig.hostnameVerifier设置。因此,如果您正在使用连接池与apache客户端http客户端配置,则应该能够使用以下代码来忽略ssl验证:
  ClientConfig clientConfig = new ClientConfig();
  // ... configure your clientConfig
  SSLContext sslContext = null;
  try {
    sslContext = SSLContext.getInstance("SSL");
    sslContext.init(null, new TrustManager[] {
        new X509TrustManager() {
          @Override
          public void checkClientTrusted(X509Certificate[] x509Certificates, String s) {
          }

          @Override
          public void checkServerTrusted(X509Certificate[] x509Certificates, String s) {
          }

          @Override
          public X509Certificate[] getAcceptedIssuers() {
            return new X509Certificate[] {};
          }
        }
    }, null);
  } catch (NoSuchAlgorithmException e) {
    //logger.debug("Ignoring 'NoSuchAlgorithmException' while ignoring ssl certificate validation.");
  } catch (KeyManagementException e) {
    //logger.debug("Ignoring 'KeyManagementException' while ignoring ssl certificate validation.");
  }
  Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
      .register("http", PlainConnectionSocketFactory.getSocketFactory())
      .register("https", new SSLConnectionSocketFactory(sslContext, new AbstractVerifier() {
        @Override
        public void verify(String host, String[] cns, String[] subjectAlts) {
        }
      }))
      .build();
  connectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
  clientConfig.property(ApacheClientProperties.CONNECTION_MANAGER, connectionManager);
  return ClientBuilder.newClient(clientConfig);

0

好的,我想只是添加我的类,因为将来可能会有一些开发人员想要连接到Netbackup服务器(或类似的服务器),并从Java中执行操作,同时忽略SSL证书。这对我有效,并且我们使用Windows活动目录对Netbackup服务器进行身份验证。

public static void main(String[] args) throws Exception {
    SSLContext sslcontext = null;
    try {
        sslcontext = SSLContext.getInstance("TLS");
    } catch (NoSuchAlgorithmException ex) {
        Logger.getLogger(main.class.getName()).log(Level.SEVERE, null, ex);
    }
    try {
        sslcontext.init(null, new TrustManager[]{new X509TrustManager() {
            public X509Certificate[] getAcceptedIssuers() {
                return new X509Certificate[0];
            }
            @Override
            public void checkClientTrusted(X509Certificate[] xcs, String string) throws CertificateException {
                //throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
            }

            @Override
            public void checkServerTrusted(X509Certificate[] xcs, String string) throws CertificateException {
                //throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
            }
        }}, new java.security.SecureRandom());
    } catch (KeyManagementException ex) {
        Logger.getLogger(main.class.getName()).log(Level.SEVERE, null, ex);
    }
    //HttpAuthenticationFeature feature = HttpAuthenticationFeature.basicBuilder().credentials(username, password).build();
    ClientConfig clientConfig = new ClientConfig();
    //clientConfig.register(feature);
    Client client = ClientBuilder.newBuilder().withConfig(clientConfig)
            .sslContext(sslcontext)
            .hostnameVerifier((s1, s2) -> true)
            .build();

    //String the_url = "https://the_server:1556/netbackup/security/cacert";
    String the_token;
    {
        String the_url = "https://the_server:1556/netbackup/login";
        WebTarget webTarget = client.target(the_url);

        Invocation.Builder invocationBuilder = webTarget.request(MediaType.APPLICATION_JSON);
        String jsonString = new JSONObject()
                .put("domainType", "NT")
                .put("domainName", "XX")
                .put("userName", "the username")
                .put("password", "the password").toString();
        System.out.println(jsonString);
        Response response = invocationBuilder.post(Entity.json(jsonString));
        String data = response.readEntity(String.class);
        JSONObject jo = new JSONObject(data);
        the_token = jo.getString("token");
        System.out.println("token is:" + the_token);

    }
    {
        String the_url = "https://the_server:1556/netbackup/admin/jobs/1122012"; //job id 1122012 is an example
        WebTarget webTarget = client.target(the_url);
        Invocation.Builder invocationBuilder = webTarget.request(MediaType.APPLICATION_JSON).header(HttpHeaders.AUTHORIZATION, the_token).header(HttpHeaders.ACCEPT, "application/vnd.netbackup+json;version=1.0");
        Response response = invocationBuilder.get();
        System.out.println("response status:" + response.getStatus());
        String data = response.readEntity(String.class);
        //JSONObject jo = new JSONObject(data);
        System.out.println(data);
    }
}

我知道这可能被视为离题,但我敢打赌,试图连接到Netbackup服务器的开发人员可能最终会来到这里。顺便说一句,非常感谢在这个问题中所有的答案! 我所说的规范是这里他们的代码示例目前缺少Java示例。

***当然,这是不安全的,因为我们忽略了证书!


0

对于Jersey 1.X

    TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {

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

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

        public java.security.cert.X509Certificate[] getAcceptedIssuers() { 
            // or you can return null too
            return new java.security.cert.X509Certificate[0];
        }
    }};


    SSLContext sc = SSLContext.getInstance("TLS");
    sc.init(null, trustAllCerts, new SecureRandom());
    HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
    HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
        public boolean verify(String string, SSLSession sslSession) {
            return true;
        }
    });

1
你也不能返回null。请参阅Javadoc。 - user207421
请测试一下,你会看到结果! - grep
有时候,你可以返回 null 以确保一切正常工作。请阅读注释。 - grep
这些都不相关。当Javadoc与它们两个相矛盾时,您无法在另一个StackOverflow答案中引用StackOverflow答案。任何在这里或其他地方返回null的答案都是错误的,就像这个答案一样。请阅读Javadoc中所说的内容,并遵守所需的契约。 - user207421

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