在java中调用不信任的https接口

如何在java中调用不安全的https接口

主要由两部分构成

  • 忽略 SSL 证书校验并声明协议为TLSv1.3
  • 禁用主机名验证
  • 下面的代码为Post实现,分别为传递body和传递表单包含文件。
  • java17使用的java.net,java8使用的javax.net

一个简单的分析方式

使用wireshark抓取对应的接口的日志,以及开启java关于tcp相关的日志打印。

java8

public String chatHttps(ChatRequest chatRequest) {
    URI uri = getUri("/api/v1/chat/completions");
    URL uriUrl;
    try {
        uriUrl = uri.toURL();
    } catch (MalformedURLException e) {
        throw new RuntimeException("获取接口失败", e);
    }
    log.info(uri.toString());
    String body;
    try {
        body = objectMapper.writeValueAsString(chatRequest);
    } catch (IOException e) {
        throw new RuntimeException("传递信息失败", e);
    }

    // 忽略 SSL 证书校验
    SSLContext sslContext = getSslContext();

    HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());

    // 禁用主机名验证
    HttpsURLConnection.setDefaultHostnameVerifier((hostname, session) -> true);
    // 打开连接并配置请求
    HttpsURLConnection connection = getHttpsUrlConnection(uriUrl, body);

    StringBuilder response = new StringBuilder();
    // 获取响应
    try (BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) {
        String inputLine;
        while ((inputLine = in.readLine()) != null) {
            response.append(inputLine);
        }
    } catch (IOException e) {
        throw new RuntimeException("读取返回消息失败", e);
    }
    return response.toString();
}

private URI getUri(String endPoint) {
    URI uri = URI.create(url);
    String basePath = uri.getPath().replaceAll("/+$", "") + endPoint;
    uri = uri.resolve(basePath);
    return uri;
}

private HttpsURLConnection getHttpsUrlConnection(URL uriUrl, String body) {
    RuntimeException connectionErrorMessage = null;
    HttpsURLConnection connection = null;
    int tryCount = 10;
    while (tryCount > 0) {
        try {
            connection = (HttpsURLConnection) uriUrl.openConnection();
            connection.setRequestMethod("POST");
            connection.setDoOutput(true);
            connection.setConnectTimeout((int) Duration.ofMinutes(2).toMillis());
            connection.setReadTimeout((int) Duration.ofMinutes(2).toMillis());
            connection.setRequestProperty("Authorization", "Bearer " + token);
            connection.setRequestProperty("Content-Type", "application/json");

            // 写入请求体
            try (OutputStream os = connection.getOutputStream()) {
                byte[] input = body.getBytes(StandardCharsets.UTF_8);
                os.write(input, 0, input.length);
            }
            break;
        } catch (IOException e) {
            connection = null;
            connectionErrorMessage = new RuntimeException("打开连接并配置请求失败", e);
        }
        tryCount--;
    }
    RuntimeException finalConnectionErrorMessage = connectionErrorMessage;
    Assert.notNull(connection, () -> finalConnectionErrorMessage);
    return connection;
}

private HttpsURLConnection getHttpsUrlConnection(URL uriUrl, MultipartFile file, Map<String, String> formFields) {
    RuntimeException connectionErrorMessage = null;
    HttpsURLConnection connection = null;
    // 生成唯一的boundary
    String boundary = "----WebKitFormBoundary" + UUID.randomUUID();
    int tryCount = 10;
    while (tryCount > 0) {
        try {
            connection = (HttpsURLConnection) uriUrl.openConnection();
            connection.setRequestMethod("POST");
            connection.setDoOutput(true);
            connection.setConnectTimeout((int) Duration.ofMinutes(2).toMillis());
            connection.setReadTimeout((int) Duration.ofMinutes(2).toMillis());
            connection.setRequestProperty("Authorization", "Bearer " + token);
            connection.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);

            // 写入请求体
            try (OutputStream os = connection.getOutputStream();
                 PrintWriter writer = new PrintWriter(new OutputStreamWriter(os, StandardCharsets.UTF_8), true)) {
                // 写入普通表单字段
                for (Map.Entry<String, String> entry : formFields.entrySet()) {
                    String formFieldName = entry.getKey();
                    String formFieldValue = entry.getValue();

                    writer.append("--").append(boundary).append("\r\n");
                    writer.append("Content-Disposition: form-data; name=\"").append(formFieldName).append("\"").append("\r\n");
                    writer.append("Content-Type: text/plain; charset=").append(String.valueOf(StandardCharsets.UTF_8)).append("\r\n");
                    writer.append("\r\n");
                    writer.append(formFieldValue).append("\r\n");
                    writer.flush();
                }

                // 写入文件的部分
                String fileName = file.getOriginalFilename();
                String contentType = file.getContentType();
                long contentLength = file.getSize();
                String encodedFileName = URLEncoder.encode(Objects.requireNonNull(fileName), UTF_8);
                writer.append("--").append(boundary).append("\r\n");
                writer.append("Content-Disposition: form-data; name=\"file\"; filename=\"")
                        .append(fileName)
                        .append("\"; filename*=UTF-8''")
                        .append(encodedFileName)  // URL 编码的文件名
                        .append("\r\n");
                writer.append("Content-Type: ").append(contentType).append("\r\n");
                writer.append("Content-Length: ").append(String.valueOf(contentLength)).append("\r\n");
                writer.append("\r\n");
                writer.flush();  // 写入文件的头部信息

                // 写入文件内容
                try (InputStream fileInputStream = file.getInputStream()) {
                    byte[] buffer = new byte[4096];
                    int bytesRead;
                    while ((bytesRead = fileInputStream.read(buffer)) != -1) {
                        os.write(buffer, 0, bytesRead);
                    }
                }

                // 写入结束边界
                writer.append("\r\n").flush();
                writer.append("--").append(boundary).append("--").append("\r\n");
            }

            break;
        } catch (IOException e) {
            connection = null;
            connectionErrorMessage = new RuntimeException("打开连接并配置请求失败", e);
        }
        tryCount--;
    }
    RuntimeException finalConnectionErrorMessage = connectionErrorMessage;
    Assert.notNull(connection, () -> finalConnectionErrorMessage);
    return connection;
}

public String uploadFileHttps(MultipartFile file) {
    URI uri = getUri("/api/file/upload");
    URL uriUrl;
    try {
        uriUrl = uri.toURL();
    } catch (MalformedURLException e) {
        throw new RuntimeException("获取接口失败", e);
    }
    Map<String, String> map = new HashMap<>(3);
    map.put("bucketName", "chat");
    map.put("metadata", "{}");
    log.info(uri.toString());


    SSLContext sslContext = getSslContext();

    HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());

    // 禁用主机名验证
    HttpsURLConnection.setDefaultHostnameVerifier((hostname, session) -> true);
    // 打开连接并配置请求
    HttpsURLConnection connection = getHttpsUrlConnection(uriUrl, file, map);

    StringBuilder response = new StringBuilder();
    // 获取响应
    try (BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) {
        String inputLine;
        while ((inputLine = in.readLine()) != null) {
            response.append(inputLine);
        }
    } catch (IOException e) {
        throw new RuntimeException("读取返回消息失败", e);
    }
    return response.toString();
}

private SSLContext getSslContext() {
    // 忽略 SSL 证书校验
    SSLContext sslContext;
    try {
        sslContext = SSLContext.getInstance("TLSv1.3");
        sslContext.init(null, new TrustManager[]{new X509TrustManager() {
            @Override
            public void checkClientTrusted(X509Certificate[] chain, String authType) {
                // 不校验客户端证书
            }

            @Override
            public void checkServerTrusted(X509Certificate[] chain, String authType) {
                // 不校验服务端证书
            }

            @Override
            public X509Certificate[] getAcceptedIssuers() {
                return new X509Certificate[0];
            }
        }}, new SecureRandom());
    } catch (NoSuchAlgorithmException | KeyManagementException e) {
        throw new RuntimeException("设置证书失败", e);
    }
    return sslContext;
}

java17

public String chatHttps(ChatRequest chatRequest) {
    URI uri = getUri("/api/v1/chat/completions");
    URL uriUrl;
    try {
        uriUrl = uri.toURL();
    } catch (MalformedURLException e) {
        throw new RuntimeException("获取接口失败", e);
    }
    log.info(uri.toString());
    String body;
    try {
        body = objectMapper.writeValueAsString(chatRequest);
    } catch (IOException e) {
        throw new RuntimeException("传递信息失败", e);
    }

    // 忽略 SSL 证书校验
    SSLContext sslContext = getSslContext();

    HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());

    // 禁用主机名验证
    HttpsURLConnection.setDefaultHostnameVerifier((hostname, session) -> true);
    // 打开连接并配置请求
    HttpsURLConnection connection = getHttpsUrlConnection(uriUrl, body);

    StringBuilder response = new StringBuilder();
    // 获取响应
    try (BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) {
        String inputLine;
        while ((inputLine = in.readLine()) != null) {
            response.append(inputLine);
        }
    } catch (IOException e) {
        throw new RuntimeException("读取返回消息失败", e);
    }
    return response.toString();
}

private URI getUri(String endPoint) {
    URI uri = URI.create(url);
    String basePath = uri.getPath().replaceAll("/+$", "") + endPoint;
    uri = uri.resolve(basePath);
    return uri;
}

private HttpsURLConnection getHttpsUrlConnection(URL uriUrl, String body) {
    var ref = new Object() {
        RuntimeException connectionErrorMessage = null;
    };
    HttpsURLConnection connection = null;
    int tryCount = 10;
    while (tryCount > 0) {
        try {
            connection = (HttpsURLConnection) uriUrl.openConnection();
            connection.setRequestMethod("POST");
            connection.setDoOutput(true);
            connection.setConnectTimeout((int) Duration.ofMinutes(2).toMillis());
            connection.setReadTimeout((int) Duration.ofMinutes(2).toMillis());
            connection.setRequestProperty("Authorization", "Bearer " + token);
            connection.setRequestProperty("Content-Type", "application/json");

            // 写入请求体
            try (OutputStream os = connection.getOutputStream()) {
                byte[] input = body.getBytes(StandardCharsets.UTF_8);
                os.write(input, 0, input.length);
            }
            break;
        } catch (IOException e) {
            connection = null;
            ref.connectionErrorMessage = new RuntimeException("打开连接并配置请求失败", e);
        }
        tryCount--;
    }
    Assert.notNull(connection, () -> ref.connectionErrorMessage);
    return connection;
}

private HttpsURLConnection getHttpsUrlConnection(URL uriUrl, MultipartFile file, Map<String, String> formFields) {
    var ref = new Object() {
        RuntimeException connectionErrorMessage = null;
    };
    HttpsURLConnection connection = null;
    // 生成唯一的boundary
    String boundary = "----WebKitFormBoundary" + UUID.randomUUID();
    int tryCount = 10;
    while (tryCount > 0) {
        try {
            connection = (HttpsURLConnection) uriUrl.openConnection();
            connection.setRequestMethod("POST");
            connection.setDoOutput(true);
            connection.setConnectTimeout((int) Duration.ofMinutes(2).toMillis());
            connection.setReadTimeout((int) Duration.ofMinutes(2).toMillis());
            connection.setRequestProperty("Authorization", "Bearer " + token);
            connection.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);

            // 写入请求体
            try (OutputStream os = connection.getOutputStream();
                 PrintWriter writer = new PrintWriter(new OutputStreamWriter(os, StandardCharsets.UTF_8), true)) {
                // 写入普通表单字段
                for (Map.Entry<String, String> entry : formFields.entrySet()) {
                    String formFieldName = entry.getKey();
                    String formFieldValue = entry.getValue();

                    writer.append("--").append(boundary).append("\r\n");
                    writer.append("Content-Disposition: form-data; name=\"").append(formFieldName).append("\"").append("\r\n");
                    writer.append("Content-Type: text/plain; charset=").append(String.valueOf(StandardCharsets.UTF_8)).append("\r\n");
                    writer.append("\r\n");
                    writer.append(formFieldValue).append("\r\n");
                    writer.flush();
                }

                // 写入文件的部分
                String fileName = file.getOriginalFilename();
                String contentType = file.getContentType();
                long contentLength = file.getSize();
                String encodedFileName = URLEncoder.encode(Objects.requireNonNull(fileName), StandardCharsets.UTF_8);
                writer.append("--").append(boundary).append("\r\n");
                writer.append("Content-Disposition: form-data; name=\"file\"; filename=\"")
                        .append(fileName)
                        .append("\"; filename*=UTF-8''")
                        .append(encodedFileName)  // URL 编码的文件名
                        .append("\r\n");
                writer.append("Content-Type: ").append(contentType).append("\r\n");
                writer.append("Content-Length: ").append(String.valueOf(contentLength)).append("\r\n");
                writer.append("\r\n");
                writer.flush();  // 写入文件的头部信息

                // 写入文件内容
                try (InputStream fileInputStream = file.getInputStream()) {
                    byte[] buffer = new byte[4096];
                    int bytesRead;
                    while ((bytesRead = fileInputStream.read(buffer)) != -1) {
                        os.write(buffer, 0, bytesRead);
                    }
                }

                // 写入结束边界
                writer.append("\r\n").flush();
                writer.append("--").append(boundary).append("--").append("\r\n");
            }

            break;
        } catch (IOException e) {
            connection = null;
            ref.connectionErrorMessage = new RuntimeException("打开连接并配置请求失败", e);
        }
        tryCount--;
    }
    Assert.notNull(connection, () -> ref.connectionErrorMessage);
    return connection;
}

public String uploadFileHttps(MultipartFile file) {
    URI uri = getUri("/api/file/upload");
    URL uriUrl;
    try {
        uriUrl = uri.toURL();
    } catch (MalformedURLException e) {
        throw new RuntimeException("获取接口失败", e);
    }
    Map<String, String> map = new HashMap<>(3);
    map.put("bucketName", "chat");
    map.put("metadata", "{}");
    log.info(uri.toString());


    SSLContext sslContext = getSslContext();

    HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());

    // 禁用主机名验证
    HttpsURLConnection.setDefaultHostnameVerifier((hostname, session) -> true);
    // 打开连接并配置请求
    HttpsURLConnection connection = getHttpsUrlConnection(uriUrl, file, map);

    StringBuilder response = new StringBuilder();
    // 获取响应
    try (BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) {
        String inputLine;
        while ((inputLine = in.readLine()) != null) {
            response.append(inputLine);
        }
    } catch (IOException e) {
        throw new RuntimeException("读取返回消息失败", e);
    }
    return response.toString();
}

private SSLContext getSslContext() {
    // 忽略 SSL 证书校验
    SSLContext sslContext;
    try {
        sslContext = SSLContext.getInstance("TLSv1.3");
        sslContext.init(null, new TrustManager[]{new X509TrustManager() {
            @Override
            public void checkClientTrusted(X509Certificate[] chain, String authType) {
                // 不校验客户端证书
            }

            @Override
            public void checkServerTrusted(X509Certificate[] chain, String authType) {
                // 不校验服务端证书
            }

            @Override
            public X509Certificate[] getAcceptedIssuers() {
                return new X509Certificate[0];
            }
        }}, new SecureRandom());
    } catch (NoSuchAlgorithmException | KeyManagementException e) {
        throw new RuntimeException("设置证书失败", e);
    }
    return sslContext;
}
posted @ 2024-12-18 15:32  CrossAutomaton  阅读(687)  评论(0)    收藏  举报