Java使用https代理(使用https.proxyPort和https.proxyHost)

17

我正在制作一个Java应用程序,它依赖于设置http.proxyPorthttp.proxyHost。有两个进程:一个是常规程序,另一个是代理。我在http.proxyPort上运行一个简单的套接字监听器(我可以控制)。就像这样:

while (true) {
    try {
    Socket connection = server.accept();

    Handler handler = new Handler(connection);
    handler.start();
    } catch (Exception ex) {
        ex.printStackTrace();
    }
}

每当“进程1”发出http请求时——例如

URL yahoo = new URL("http://www.google.ca/");
URLConnection yc = yahoo.openConnection();
System.out.println(yc.getClass().getName());
BufferedReader in = new BufferedReader(new InputStreamReader(yc.getInputStream()));

它通过代理进行访问。那么如果客户端使用的是HTTPS协议呢?比如改用https://google.ca?有一个属性叫做https.proxyPorthttps.proxyHost,但我已经尝试了几个月(断断续续的,不太重要),但没有成功。我阅读了很多帖子(我会在最后列出一些,以便您知道我做了些什么)。

到目前为止,我最接近的尝试:

服务器

try {
    System.setProperty("javax.net.ssl.keyStore", "test.jks");
    System.setProperty("javax.net.ssl.keyStorePassword", "2520xe");

    SSLServerSocketFactory sslserversocketfactory =
            (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
    SSLServerSocket sslserversocket =
            (SSLServerSocket) sslserversocketfactory.createServerSocket(9999);
    System.out.println("Ready");
    SSLSocket sslsocket = (SSLSocket) sslserversocket.accept();

    InputStream inputstream = sslsocket.getInputStream();
    InputStreamReader inputstreamreader = new InputStreamReader(inputstream);
    BufferedReader bufferedreader = new BufferedReader(inputstreamreader);

    OutputStream toClient = sslsocket.getOutputStream();
    toClient.write(("HTTP/1.0 200 Connection established\n" +
            "Content-Length: " + "Shut down!".getBytes().length
                                     + "\r\n").getBytes("utf-8"));
    toClient.write("Shut down!".getBytes("utf-8"));
    toClient.close();
} catch (Exception exception) {
    exception.printStackTrace();
}

客户端

try {
    System.setProperty("https.proxyHost", "127.0.0.1");
    System.setProperty("https.proxyPort", "9999");
    URL yahoo = new URL("https://www.google.ca/");
    URLConnection yc = yahoo.openConnection();
    System.out.println(yc.getClass().getName());
    BufferedReader in = new BufferedReader(
                new InputStreamReader(
                yc.getInputStream()));
    String inputLine;

    while ((inputLine = in.readLine()) != null) 
    System.out.println(inputLine);
    in.close();
} catch (Exception ex) {
    ex.printStackTrace();
}

javax.net.ssl.SSLException: Unrecognized SSL message, plaintext connection?出现了错误,我查了一下,但是找到的都是关于邮件方面的东西。

基本上,我需要创建一个Java代理服务器,通过https。proxyPorthttps.proxyHost标志设置给客户端,并且可以将数据发送回客户端应用程序,该应用程序可能无法以任何方式进行修改(只使用URL connection = new URL("https://...")

我尝试了几个网站...


目前我也在努力设置系统属性 https.proxyHost。通过将调试器附加到 VM 进程,似乎 VM 根本没有获取 https.proxyHost 属性。 - Christian K.
好的,现在我只是使用代理主机和代理端口,而不带http或https前缀就可以正常工作了。似乎从Java 6到Java 7,这种行为又发生了变化。上述语句适用于Java 7。 - Christian K.
当客户端通过代理请求https URL时,必须发送CONNECT www.google.ca:443 HTTP/1.0请求,并且代理服务器以明文方式响应HTTP/1.0 200 Connection established。然后客户端开始SSL握手过程。 - auntyellow
2个回答

14

如 auntyellow 评论所述:您无需自行进行任何SSL篡改。基本上,https代理是将二进制数据在两个方之间转发的过程。

引用 draft-luotonen-web-proxy-tunneling-01.txt:

 CLIENT -> SERVER                        SERVER -> CLIENT
 --------------------------------------  -----------------------------------
 CONNECT home.netscape.com:443 HTTP/1.0
 User-agent: Mozilla/4.0
 <<< empty line >>>
                                         HTTP/1.0 200 Connection established
                                         Proxy-agent: Netscape-Proxy/1.1
                                         <<< empty line >>>
              <<< data tunneling to both directions begins >>>
基本上你需要确保你足够信任客户端,以便从你的代理防火墙位置连接到给定的主机和端口。因此,常见做法是限制允许的端口为443,拒绝与本地主机和来自“不受信任”的各方的连接。
这是一个“简单”的服务器,如果你还没有被说服,它可以在Java中用作https.proxy
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Created for https://dev59.com/8mQo5IYBdhLWcg3wE70J.
 */
public class Server extends Thread {

    public static void main(String[] args) {
        (new Server()).run();
    }

    public Server() {
        super("Server Thread");
    }

    @Override
    public void run() {
        try (ServerSocket serverSocket = new ServerSocket(9999)) {
            Socket socket;
            try {
                while ((socket = serverSocket.accept()) != null) {
                    (new Handler(socket)).start();
                }
            } catch (IOException e) {
                e.printStackTrace();  // TODO: implement catch
            }
        } catch (IOException e) {
            e.printStackTrace();  // TODO: implement catch
            return;
        }
    }

    public static class Handler extends Thread {
        public static final Pattern CONNECT_PATTERN = Pattern.compile("CONNECT (.+):(.+) HTTP/(1\\.[01])",
                                                                      Pattern.CASE_INSENSITIVE);
        private final Socket clientSocket;
        private boolean previousWasR = false;

        public Handler(Socket clientSocket) {
            this.clientSocket = clientSocket;
        }

        @Override
        public void run() {
            try {
                String request = readLine(clientSocket);
                System.out.println(request);
                Matcher matcher = CONNECT_PATTERN.matcher(request);
                if (matcher.matches()) {
                    String header;
                    do {
                        header = readLine(clientSocket);
                    } while (!"".equals(header));
                    OutputStreamWriter outputStreamWriter = new OutputStreamWriter(clientSocket.getOutputStream(),
                                                                                   "ISO-8859-1");

                    final Socket forwardSocket;
                    try {
                        forwardSocket = new Socket(matcher.group(1), Integer.parseInt(matcher.group(2)));
                        System.out.println(forwardSocket);
                    } catch (IOException | NumberFormatException e) {
                        e.printStackTrace();  // TODO: implement catch
                        outputStreamWriter.write("HTTP/" + matcher.group(3) + " 502 Bad Gateway\r\n");
                        outputStreamWriter.write("Proxy-agent: Simple/0.1\r\n");
                        outputStreamWriter.write("\r\n");
                        outputStreamWriter.flush();
                        return;
                    }
                    try {
                        outputStreamWriter.write("HTTP/" + matcher.group(3) + " 200 Connection established\r\n");
                        outputStreamWriter.write("Proxy-agent: Simple/0.1\r\n");
                        outputStreamWriter.write("\r\n");
                        outputStreamWriter.flush();

                        Thread remoteToClient = new Thread() {
                            @Override
                            public void run() {
                                forwardData(forwardSocket, clientSocket);
                            }
                        };
                        remoteToClient.start();
                        try {
                            if (previousWasR) {
                                int read = clientSocket.getInputStream().read();
                                if (read != -1) {
                                    if (read != '\n') {
                                        forwardSocket.getOutputStream().write(read);
                                    }
                                    forwardData(clientSocket, forwardSocket);
                                } else {
                                    if (!forwardSocket.isOutputShutdown()) {
                                        forwardSocket.shutdownOutput();
                                    }
                                    if (!clientSocket.isInputShutdown()) {
                                        clientSocket.shutdownInput();
                                    }
                                }
                            } else {
                                forwardData(clientSocket, forwardSocket);
                            }
                        } finally {
                            try {
                                remoteToClient.join();
                            } catch (InterruptedException e) {
                                e.printStackTrace();  // TODO: implement catch
                            }
                        }
                    } finally {
                        forwardSocket.close();
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();  // TODO: implement catch
            } finally {
                try {
                    clientSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();  // TODO: implement catch
                }
            }
        }

        private static void forwardData(Socket inputSocket, Socket outputSocket) {
            try {
                InputStream inputStream = inputSocket.getInputStream();
                try {
                    OutputStream outputStream = outputSocket.getOutputStream();
                    try {
                        byte[] buffer = new byte[4096];
                        int read;
                        do {
                            read = inputStream.read(buffer);
                            if (read > 0) {
                                outputStream.write(buffer, 0, read);
                                if (inputStream.available() < 1) {
                                    outputStream.flush();
                                }
                            }
                        } while (read >= 0);
                    } finally {
                        if (!outputSocket.isOutputShutdown()) {
                            outputSocket.shutdownOutput();
                        }
                    }
                } finally {
                    if (!inputSocket.isInputShutdown()) {
                        inputSocket.shutdownInput();
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();  // TODO: implement catch
            }
        }

        private String readLine(Socket socket) throws IOException {
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            int next;
            readerLoop:
            while ((next = socket.getInputStream().read()) != -1) {
                if (previousWasR && next == '\n') {
                    previousWasR = false;
                    continue;
                }
                previousWasR = false;
                switch (next) {
                    case '\r':
                        previousWasR = true;
                        break readerLoop;
                    case '\n':
                        break readerLoop;
                    default:
                        byteArrayOutputStream.write(next);
                        break;
                }
            }
            return byteArrayOutputStream.toString("ISO-8859-1");
        }
    }
}

还不错。有一个小问题。当代理从一个套接字读取EOS时,它应该关闭另一个套接字的输出并退出该线程。当它关闭了两个套接字后,它可以关闭它们并退出第二个线程。这对于HTTP keep-alive情况很有帮助。 - user207421
@EJP感谢您的反馈!我已经切换到调用关闭方法,并在forwardSocket中添加了一个finally-closer。我还漏掉了什么吗? - TheConstructor
1
我刚刚使用了这个方法,并做了一个小技巧来解决创建HTTP代理路由流量的问题,使用另一个socks代理...java -DproxySet=true -DsocksProxyHost=127.0.0.1 -DsocksProxyPort=8080 HttpServer我的需求是:我有可用的socks代理来路由流量,但许多应用程序不支持socks,因此我需要一个适配器作为http代理并将流量传输到socks代理... Java已经内置了对socks代理适配器的支持,而这个程序则充当htttp代理,问题迎刃而解... - Venkateswara Rao

1

默认的Java SE7实现用于HTTPS协议的URLConnection使用参数https.proxyHosthttps.proxyPort

添加到Tomcat:

-Dhttps.proxyHost="192.168.121.31" -Dhttps.proxyPort="3128"


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接