【问题标题】:Making Apache HttpClient 4.3 work with sslSocketFactory/HostnameVerifier使 Apache HttpClient 4.3 与 sslSocketFactory/HostnameVerifier 一起工作
【发布时间】:2014-05-07 20:20:36
【问题描述】:

我正在开发一个 Java 程序,该程序会将 POST 请求发送到我的公司使用的网站。我们不拥有该网站,它们与我们分开。我一直在用各种方法来实际传递它想要的非常挑剔的参数,以便我从程序中对其进行处理(而不是手动进行)。

我发现 Apache HttpClient 4.3 似乎是我实际尝试访问它的最佳途径,任何事情都会导致网站的愤怒响应告诉我我的用户名和密码并且无效/授权。

但后来我收到一个错误,因为站点证书不匹配,我联系了他们的支持人员,据报道他们与另一个站点共享一个基础架构,因此预计证书不匹配。

所以我使用命令行生成了一个密钥库,将其传递给程序,然后得到错误“java.security.cert.CertificateException:没有主题替代 DNS 名称匹配”。

一些搜索让我使用了一个验证器,它消除了错误。

然后我意识到我不能让 URLConnection/HttpsURLConnection 和 HttpClient/HttpPost 一起工作。这就是我卡住的地方。我不确定如何使处理我的密钥库、TrustManager、SSLSocketFactory 等的代码连接到我实际必须连接和 POST 的部分。

处理证书和验证的代码:

  InputStream in = new FileInputStream(new File("C:\\Program Files (x86)\\Java\\jre7\\bin\\my.keystore")); 
   KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); 
    ks.load(in, "blahblah".toCharArray()); 
in.close(); TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); 
    tmf.init(ks);
     X509TrustManager defaultTrustManager = (X509TrustManager)tmf.getTrustManagers()[0]; 
     SSLContext context = SSLContext.getInstance("TLS"); 
     context.init(null, new TrustManager[] {defaultTrustManager}, null); 
     javax.net.ssl.SSLSocketFactory sslSocketFactory = context.getSocketFactory();

URL url = new URL("https://emailer.driveclick.com/dbadmin/xml_post.pl"); 

     URLConnection con = url.openConnection(); 
    ((HttpsURLConnection) con).setSSLSocketFactory(sslSocketFactory); 
   ((HttpsURLConnection) con).setHostnameVerifier(new Verifier());
     con.connect(); 
    in = con.getInputStream();

应该将我连接到网站的代码:

 try {
        //log into the website

        String url2 = "https://emailer.driveclick.com/dbadmin/xml_post.pl";
        HttpClient client = HttpClientBuilder.create().build();



        HttpPost post = new HttpPost(url2);


        post.setHeader("User-Agent", USER_AGENT);

        List<BasicNameValuePair> urlParameters = new ArrayList<>();
        urlParameters.add(new BasicNameValuePair("username", "namefoo"));
        urlParameters.add(new BasicNameValuePair("api_password", "passfoo"));

        post.setEntity(new UrlEncodedFormEntity(urlParameters));

        org.apache.http.HttpResponse response = client.execute(post);

        System.out.println("\nSending 'POST' request to URL : " + url2);
        System.out.println("Post parameters : " + post.getEntity());
        System.out.println("Response Code : " + response.getStatusLine().getStatusCode());

        BufferedReader rd = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
        StringBuffer result = new StringBuffer();
        String line = "";
        while ((line = rd.readLine()) != null)
        {
            result.append(line);
        }
        System.out.println(result.toString());
    } catch (UnsupportedEncodingException ex) {
        Logger.getLogger(LastFileMove.class.getName()).log(Level.SEVERE, null, ex);
    } catch (IOException ex) {
        Logger.getLogger(LastFileMove.class.getName()).log(Level.SEVERE, null, ex);
    }

编辑:我忘了包括我为我引用的验证器创建的小类。

public class Verifier implements HostnameVerifier 
{

public boolean verify(String arg0, SSLSession arg1) {
        return true;   // mark everything as verified
}
}

2014 年 5 月 8 日更新 SSLConext 和 Verifier 现在设置如下:

SSLContext sslContext = SSLContexts.custom()
    .useTLS()
    .loadTrustMaterial(ks)
    .build(); 
X509HostnameVerifier verifier = new AbstractVerifier() 
{
@Override
public void verify(final String host, final String[] 
cns, final String[] subjectAlts) throws SSLException 
{
    verify(host, cns, subjectAlts, true);
}
};

我已经将我的 HttpClient 更改为可关闭的:

CloseableHttpClient client = HttpClients.custom()
sslSocketFactory)
.setHostnameVerifier(verifier)
.setSslcontext(sslContext)
.build();

我又回到了“javax.net.ssl.SSLException:证书中的主机名不匹配”错误。有什么建议吗?

【问题讨论】:

  • 您是否尝试过将 SSLContext 实例传递给HttpClientBuilder?我以前也成功过。
  • @vanOekel 如果我这样做,我会收到“ java.security.cert.CertificateException: No subject Alternative DNS name”错误。看来我必须将验证程序传递给 HttpClientBuilder,但我不太确定该怎么做。
  • setHostnameVerifier 是一个开始,如果你使用AllowAllHostnameVerifier,看看它是否有效。 Apache HttpClient 决定基于this制作自己的验证器
  • @vanOekel 是否允许所有危险?还是我把它和别的东西混淆了?它不会为中间攻击的人打开你的大门吗?
  • 是的,非常危险且完全不安全。但它可用于在测试期间确定问题出在哪里。

标签: java ssl ssl-certificate apache-commons apache-httpclient-4.x


【解决方案1】:

我不知道Verifier 是如何实现的,但是这段代码 sn-p 演示了如何创建自定义主机名验证器,而 HttpClient 附带的这些验证器都不符合他们的需求

KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
InputStream in = new FileInputStream(new File("C:\\Program Files (x86)\\Java\\jre7\\bin\\my.keystore"));
try {
    ks.load(in, "blahblah".toCharArray());
} finally {
    in.close();
}
SSLContext sslContext = SSLContexts.custom()
        .useTLS()
        .loadTrustMaterial(ks)
        .build();
X509HostnameVerifier verifier = new AbstractVerifier() {

    @Override
    public void verify(final String host, final String[] cns, final String[] subjectAlts) throws SSLException {
        verify(host, cns, subjectAlts, true);
    }

};

CloseableHttpClient hc = HttpClients.custom()
        .setSslcontext(sslContext)
        .setHostnameVerifier(verifier)
    .build();

【讨论】:

  • 为了清楚起见,在我的帖子中添加了验证器实现,尝试看看您的建议现在是否有效。
  • 好的,我设置了 sslContext 和验证程序,并将它们添加到 CloseableHttpClient 中,如您的示例所示......现在我回到获取“javax.net.ssl.SSLException:证书中的主机名不匹配”错误。
  • 等一下,我可以让那个验证器保持原样,我需要为它提供值吗?我想这可能是我的问题所在。
  • 但它们真的匹配吗?您的目标主机是什么,证书中的常用名称和替代名称是什么?
  • 它们不匹配。这是一开始的问题,我必须在不把安全性丢到马桶上的情况下克服它。我正在尝试访问“emailer.driveclick.com”,所有者 cn 是“www.icebase.com”
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-01-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多