使用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(); } } }
关键说明
- CONNECT请求处理:HTTPS代理的核心是隧道,浏览器先发送CONNECT请求要求代理建立到目标服务器的连接,代理返回200响应后,客户端才会发起SSL握手。
- SSL连接升级:不能一开始就用SSLServerSocket,必须先完成CONNECT隧道的明文握手,再将两端的Socket升级为SSL连接。
- 双向转发:HTTPS数据是加密的,代理无需解析请求/响应内容,只需双向转发字节流即可(如需日志,可在转发时打印原始字节或握手信息)。
内容的提问来源于stack exchange,提问作者Lorena Nuñez
相关产品推荐
相关产品推荐

