HttpsUrlConnection 双向证书验证时报错

发布于 2022-09-11 16:38:44 字数 6977 浏览 33 评论 0

服务器 API 需要进行双向证书验证(类似于微信支付), 使用 HttpClient 时返回值正常, 但是换成 HttpsUrlConnection 或者 OkHttp3 服务器就会返回错误码 400.
HttpsUrlConnection 代码如下:

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.KeyStore;
import java.security.SecureRandom;

// FIXME 请求返回 400, 不知道哪里有问题
public class HUC {

    public static void main(String... args) throws Exception {

        // 请求地址
        final String requestUrl = "https://example.com";
        // POST 请求体数据
        final String requestBody = "{\"loginName\":\"admin\", \"password\":\"admin\"}";
        // 私钥证书文件路径
        final String certFile = "D:\\Downloads\\test.pfx";
        // 私钥证书密码
        final String password = "drowssap";

        // SSL 相关配置
        KeyStore keyStore = KeyStore.getInstance("PKCS12");
        try (FileInputStream fileInputStream = new FileInputStream(certFile)) {
            keyStore.load(fileInputStream, password.toCharArray());
        }
        KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
        keyManagerFactory.init(keyStore, password.toCharArray());
        SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
        sslContext.init(keyManagerFactory.getKeyManagers(), null, new SecureRandom());

        // 开始连接服务器
        String response;
        URL url = new URL(requestUrl);
        HttpsURLConnection httpsURLConnection = null;
        try {
            httpsURLConnection = (HttpsURLConnection) url.openConnection();

            // SSL 相关
            httpsURLConnection.setHostnameVerifier((hostname, sslSession) -> true);
            httpsURLConnection.setSSLSocketFactory(sslContext.getSocketFactory());

            httpsURLConnection.setRequestMethod("POST");
            httpsURLConnection.setUseCaches(false);
            httpsURLConnection.setDoOutput(true);
            httpsURLConnection.setDoInput(true);
            httpsURLConnection.setConnectTimeout(5000);
            httpsURLConnection.setReadTimeout(5000);
            httpsURLConnection.setRequestProperty("Content-Type", "text/json; charset=utf-8");
            
            OutputStream outputStream = httpsURLConnection.getOutputStream();
            outputStream.write(requestBody.getBytes(StandardCharsets.UTF_8));
            outputStream.flush();

            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            InputStream inputStream = httpsURLConnection.getInputStream();
            byte[] buffer = new byte[1024];
            int length;
            while ((length = inputStream.read(buffer)) != -1) {
                byteArrayOutputStream.write(buffer, 0, length);
            }
            response = new String(byteArrayOutputStream.toByteArray(), StandardCharsets.UTF_8);
        } finally {
            if (httpsURLConnection != null) {
                httpsURLConnection.disconnect();
            }
        }

        System.out.print(response);
    }
}

OkHttp3 与 HttpsUrlConnection 一样, 返回错误码 400, 所以代码就不贴了.

HttpClient 是正常的, 代码如下

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.DefaultHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.BasicHttpClientConnectionManager;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.KeyStore;
import java.security.SecureRandom;

public class HC {

    public static void main(String[] args) throws Exception {
        BasicHttpClientConnectionManager connManager;

        char[] password = "drowssap".toCharArray();
        FileInputStream fileInputStream = new FileInputStream("D:\\Downloads\\test.pfx");
        KeyStore ks = KeyStore.getInstance("PKCS12");
        ks.load(fileInputStream, password);
        fileInputStream.close();

        KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        kmf.init(ks, password);

        SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
        sslContext.init(kmf.getKeyManagers(), null, new SecureRandom());

        SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(
                sslContext,
                new String[]{"TLSv1.2"},
                null,
                new DefaultHostnameVerifier());

        connManager = new BasicHttpClientConnectionManager(
                RegistryBuilder.<ConnectionSocketFactory>create()
                        .register("http", PlainConnectionSocketFactory.getSocketFactory())
                        .register("https", sslConnectionSocketFactory)
                        .build(),
                null,
                null,
                null
        );

        HttpClient httpClient = HttpClientBuilder.create()
                .setConnectionManager(connManager)
                .build();

        String url = "https://example.com";
        HttpPost httpPost = new HttpPost(url);

        RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(5000).setConnectTimeout(5000).build();
        httpPost.setConfig(requestConfig);

        StringEntity postEntity = new StringEntity("{\"loginName\":\"admin\", \"password\":\"admin\"}", "UTF-8");
        httpPost.addHeader("Content-Type", "text/json");
        httpPost.setEntity(postEntity);

        HttpResponse httpResponse = httpClient.execute(httpPost);
        HttpEntity httpEntity = httpResponse.getEntity();

        InputStream inputStream = httpEntity.getContent();
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        byte[] bytes = new byte[1024];
        int length;
        while ((length = inputStream.read(bytes)) != -1) {
            byteArrayOutputStream.write(bytes, 0, length);
        }

        System.out.println(new String(byteArrayOutputStream.toByteArray(), StandardCharsets.UTF_8));
    }
}

Google 了很久, 唯一找到的一点头绪是微信开发文档中对于 Http 框架选用的说明 -> 传送门, 似乎有点符合情况

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文