You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用SSLSocket实现Java代理时遭遇SSLException异常求助

解决javax.net.ssl.SSLException: Unsupported or unrecognized SSL message异常

问题根源

浏览器配置HTTP代理访问HTTPS网站时,首先会发送明文的CONNECT请求来建立隧道,而你的代码直接用SSLServerSocket监听,要求客户端一开始就发起SSL握手,导致收到普通HTTP报文时触发SSL解析异常。这是HTTP代理处理HTTPS请求的核心逻辑错误,和SSL配置本身无关。

修复方案

先通过普通ServerSocket接收客户端的CONNECT请求,完成隧道握手后,再分别与客户端、目标服务器建立SSL连接,最后实现双向数据转发。

修改后的主方法

public static void main(String... args) {
    RequestHandler handler = new RequestHandler();
    try {
        // 用普通ServerSocket监听,先处理CONNECT请求
        ServerSocket serverSocket = new ServerSocket(8080);
        while (true) {
            Socket clientSocket = serverSocket.accept();
            Runnable action = () -> handler.handleRequest(clientSocket);
            Thread thread = new Thread(action);
            thread.start();
        }
    } catch (IOException ex) {
        log.error(ex.getMessage(), ex);
    }
}

修改后的RequestHandler类

@Slf4j
@NoArgsConstructor
public class RequestHandler {

    public void handleRequest(Socket clientSocket) {
        try {
            RawHttp http = new RawHttp();
            RawHttpRequest connectRequest = http.parseRequest(clientSocket.getInputStream());

            // 区分处理CONNECT请求和普通HTTP请求
            if (!"CONNECT".equals(connectRequest.getMethod())) {
                handleHttpRequest(clientSocket, connectRequest);
                return;
            }

            // 解析目标主机与端口
            String targetHostPort = connectRequest.getUri().getPath();
            String[] hostPort = targetHostPort.split(":");
            String host = hostPort[0];
            int port = hostPort.length > 1 ? Integer.parseInt(hostPort[1]) : 443;

            // 连接目标服务器
            Socket serverSocket = new Socket(host, port);

            // 返回200响应,确认隧道建立
            RawHttpResponse tunnelResponse = new RawHttpResponse(
                    new StartLine("HTTP/1.1", 200, "Connection Established"),
                    Headers.empty(),
                    Body.empty()
            );
            tunnelResponse.writeTo(clientSocket.getOutputStream());

            // 将客户端、服务器的Socket升级为SSL连接
            SSLSocket clientSslSocket = upgradeToSsl(clientSocket, host);
            SSLSocket serverSslSocket = upgradeToSsl(serverSocket, host);

            // 启动双向数据转发线程
            startForwarding(clientSslSocket.getInputStream(), serverSslSocket.getOutputStream());
            startForwarding(serverSslSocket.getInputStream(), clientSslSocket.getOutputStream());

        } catch (IOException ex) {
            log.error(ex.getMessage(), ex);
        }
    }

    private SSLSocket upgradeToSsl(Socket socket, String host) throws IOException {
        SSLSocketFactory factory = (SSLSocketFactory) SSLSocketFactory.getDefault();
        SSLSocket sslSocket = (SSLSocket) factory.createSocket(
                socket, host, socket.getPort(), true // 重用现有Socket并启动SSL握手
        );
        sslSocket.startHandshake();
        return sslSocket;
    }

    private void startForwarding(InputStream in, OutputStream out) {
        new Thread(() -> {
            byte[] buffer = new byte[8192];
            int readLen;
            try {
                while ((readLen = in.read(buffer)) != -1) {
                    out.write(buffer, 0, readLen);
                    out.flush();
                    // 此处可添加请求/响应日志打印逻辑
                }
            } catch (IOException e) {
                // 连接断开属于正常场景,无需额外报错日志
            } finally {
                try {
                    in.close();
                    out.close();
                } catch (IOException e) {
                    log.error("关闭流失败", e);
                }
            }
        }).start();
    }

    // 可选:处理普通HTTP请求的逻辑
    private void handleHttpRequest(Socket clientSocket, RawHttpRequest request) throws IOException {
        String host = request.getUri().getHost();
        int port = request.getUri().getPort() != -1 ? request.getUri().getPort() : 80;

        try (Socket serverSocket = new Socket(host, port)) {
            request.writeTo(serverSocket.getOutputStream());
            RawHttp http = new RawHttp();
            RawHttpResponse response = http.parseResponse(serverSocket.getInputStream());
            response.writeTo(clientSocket.getOutputStream());
        } finally {
            clientSocket.close();
        }
    }
}

关键说明

  1. CONNECT请求处理:HTTPS代理的核心是隧道,浏览器先发送CONNECT请求要求代理建立到目标服务器的连接,代理返回200响应后,客户端才会发起SSL握手。
  2. SSL连接升级:不能一开始就用SSLServerSocket,必须先完成CONNECT隧道的明文握手,再将两端的Socket升级为SSL连接。
  3. 双向转发:HTTPS数据是加密的,代理无需解析请求/响应内容,只需双向转发字节流即可(如需日志,可在转发时打印原始字节或握手信息)。

内容的提问来源于stack exchange,提问作者Lorena Nuñez

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 00:20:08