HTTPS URL的基本代理身份验证返回HTTP / 1.0 407必需的代理身份验证

约翰

我想对Java中的连接(仅此连接)使用具有基本身份验证(用户名,密码)的代理。以下代码适用于HTTP URL(例如“ http://www.google.com ”):

URL url = new URL("http://www.google.com");
HttpURLConnection httpURLConnection = null;
InetSocketAddress proxyLocation = new InetSocketAddress(proxyHost, proxyPort);
Proxy proxy = new Proxy(Proxy.Type.HTTP, proxyLocation);
httpURLConnection = (HttpURLConnection) url.openConnection(proxy);
// Works for HTTP only! Doesn't work for HTTPS!
String encoded = new sun.misc.BASE64Encoder().encodeBuffer((proxyUserName + ":" + proxyPassword).getBytes()).replace("\r\n", "");
httpURLConnection.setRequestProperty("Proxy-Authorization", "Basic " + encoded);
InputStream is = httpURLConnection.getInputStream();
InputStreamReader isr = new InputStreamReader(is); 
int data = isr.read();
while(data != -1){
  char c = (char) data;
  data = isr.read();
  System.out.print(c);
}
isr.close();

但是,该代码不适用于HTTPS URL(例如“ https://www.google.com ”)!我收到java.io.IOException: Unable to tunnel through proxy. Proxy returns "HTTP/1.0 407 Proxy Authentication Required"的时候我尝试访问HTTPS URL。

此代码适用于HTTP和HTTPS:

URL url = new URL("https://www.google.com");
HttpURLConnection httpURLConnection = null;
InetSocketAddress proxyLocation = new InetSocketAddress(proxyHost, proxyPort);
Proxy proxy = new Proxy(Proxy.Type.HTTP, proxyLocation);
httpURLConnection = (HttpURLConnection) url.openConnection(proxy);
// Works for HTTP and HTTPS, but sets a global default!
Authenticator.setDefault(new Authenticator() {
  protected PasswordAuthentication getPasswordAuthentication() {
    return new PasswordAuthentication(proxyUserName, proxyPassword.toCharArray());
  }
});
InputStream is = httpURLConnection.getInputStream();
InputStreamReader isr = new InputStreamReader(is); 
int data = isr.read();
while(data != -1){
  char c = (char) data;
  data = isr.read();
  System.out.print(c);
}
isr.close();

第二个代码的问题是它设置了一个新的默认值,Authenticator而我不想这样做,因为此代理仅由应用程序的一部分使用,而应用程序的不同部分可能正在使用其他代理。我不想为整个应用程序设置全局默认值。有没有一种方法可以让第一个代码与HTTPS一起使用,或者有一种方法可以在Authenticator不将其设置为默认值的情况下使用它?

我必须使用java.net.HttpURLConnection,因为我要重写必须返回an的类的方法HttpURLConnection,所以我不能使用Apache HttpClient。

费里比格

您可以自己扩展ProxiedHttpsConnection和处理所有与底层相关的内容。

需要执行以下步骤以通过HTTP代理连接到https网站:

注意:与代理服务器和http服务器的通信应使用ASCII7

  1. 发送CONNECT stackoverflow.com:443 HTTP/1.0\r\n给代理
  2. 发送您的身份验证:Proxy-Authorization: Basic c2F5WW91SGF2ZVNlZW5UaGlzSW5UaGVDb21tZW50cw==\r\n
  3. 结束第一个请求: \r\n
  4. 从代理读取响应,直到看到组合“ \ r \ n \ r \ n”。
  5. 解析您从代理获得的响应的第一行,并检查其是否以开头HTTP/1.0 200
  6. 在现有连接上就地启动SSL会话。
  7. 发送http请求的开始: GET /questions/3304006/persistent-httpurlconnection-in-java HTTP/1.0\r\n
  8. 设置正确的主机头: Host: stackoverflow.com\r\n
  9. 将请求结束到http服务器: \r\n
  10. 阅读直到\r\n并将第一行解析为状态消息
  11. 读取直到流结束以获取请求正文

当我们想要实现HttpUrlConnection类时,我们还需要考虑以下几点:

  • 在构造类时,该类应存储数据以用于将来的连接,但不要直接使其
  • 可以按任何顺序调用任何方法
  • 闭合OutputStream意味着数据传输已经完成,而不是意味着连接必须完成
  • 每个api以不同的顺序使用方法
  • HTTP标头不区分大小写,Java映射区分大小写。

迅速地说,有很多陷阱

在我设计的类中,它使用布尔标志来记住是否调用connect方法,并且该afterPostClosure方法还具有getInputStream()OutputStream关闭之前调用if的支持

此类还对套接字返回的流使用了尽可能少的包装,以防止过于复杂。

public class ProxiedHttpsConnection extends HttpURLConnection {

    private final String proxyHost;
    private final int proxyPort;
    private static final byte[] NEWLINE = "\r\n".getBytes();//should be "ASCII7"

    private Socket socket;
    private final Map<String, List<String>> headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
    private final Map<String, List<String>> sendheaders = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
    private final Map<String, List<String>> proxyheaders = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
    private final Map<String, List<String>> proxyreturnheaders = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
    private int statusCode;
    private String statusLine;
    private boolean isDoneWriting;

    public ProxiedHttpsConnection(URL url,
            String proxyHost, int proxyPort, String username, String password)
            throws IOException {
        super(url);
        socket = new Socket();
        this.proxyHost = proxyHost;
        this.proxyPort = proxyPort;
        String encoded = Base64.encode((username + ":" + password).getBytes())
                .replace("\r\n", "");
        proxyheaders.put("Proxy-Authorization", new ArrayList<>(Arrays.asList("Basic " + encoded)));
    }

    @Override
    public OutputStream getOutputStream() throws IOException {
        connect();
        afterWrite();
        return new FilterOutputStream(socket.getOutputStream()) {
            @Override
            public void write(byte[] b, int off, int len) throws IOException {
                out.write(String.valueOf(len).getBytes());
                out.write(NEWLINE);
                out.write(b, off, len);
                out.write(NEWLINE);
            }

            @Override
            public void write(byte[] b) throws IOException {
                out.write(String.valueOf(b.length).getBytes());
                out.write(NEWLINE);
                out.write(b);
                out.write(NEWLINE);
            }

            @Override
            public void write(int b) throws IOException {
                out.write(String.valueOf(1).getBytes());
                out.write(NEWLINE);
                out.write(b);
                out.write(NEWLINE);
            }

            @Override
            public void close() throws IOException {
                afterWrite();
            }

        };
    }

    private boolean afterwritten = false;

    @Override
    public InputStream getInputStream() throws IOException {
        connect();
        return socket.getInputStream();

    }

    @Override
    public void setRequestMethod(String method) throws ProtocolException {
        this.method = method;
    }

    @Override
    public void setRequestProperty(String key, String value) {
        sendheaders.put(key, new ArrayList<>(Arrays.asList(value)));
    }

    @Override
    public void addRequestProperty(String key, String value) {
        sendheaders.computeIfAbsent(key, l -> new ArrayList<>()).add(value);
    }

    @Override
    public Map<String, List<String>> getHeaderFields() {
        return headers;
    }

    @Override
    public void connect() throws IOException {
        if (connected) {
            return;
        }
        connected = true;
        socket.setSoTimeout(getReadTimeout());
        socket.connect(new InetSocketAddress(proxyHost, proxyPort), getConnectTimeout());
        StringBuilder msg = new StringBuilder();
        msg.append("CONNECT ");
        msg.append(url.getHost());
        msg.append(':');
        msg.append(url.getPort() == -1 ? 443 : url.getPort());
        msg.append(" HTTP/1.0\r\n");
        for (Map.Entry<String, List<String>> header : proxyheaders.entrySet()) {
            for (String l : header.getValue()) {
                msg.append(header.getKey()).append(": ").append(l);
                msg.append("\r\n");
            }
        }

        msg.append("Connection: close\r\n");
        msg.append("\r\n");
        byte[] bytes;
        try {
            bytes = msg.toString().getBytes("ASCII7");
        } catch (UnsupportedEncodingException ignored) {
            bytes = msg.toString().getBytes();
        }
        socket.getOutputStream().write(bytes);
        socket.getOutputStream().flush();
        byte reply[] = new byte[200];
        byte header[] = new byte[200];
        int replyLen = 0;
        int headerLen = 0;
        int newlinesSeen = 0;
        boolean headerDone = false;
        /* Done on first newline */
        InputStream in = socket.getInputStream();
        while (newlinesSeen < 2) {
            int i = in.read();
            if (i < 0) {
                throw new IOException("Unexpected EOF from remote server");
            }
            if (i == '\n') {
                if (newlinesSeen != 0) {
                    String h = new String(header, 0, headerLen);
                    String[] split = h.split(": ");
                    if (split.length != 1) {
                        proxyreturnheaders.computeIfAbsent(split[0], l -> new ArrayList<>()).add(split[1]);
                    }
                }
                headerDone = true;
                ++newlinesSeen;
                headerLen = 0;
            } else if (i != '\r') {
                newlinesSeen = 0;
                if (!headerDone && replyLen < reply.length) {
                    reply[replyLen++] = (byte) i;
                } else if (headerLen < reply.length) {
                    header[headerLen++] = (byte) i;
                }
            }
        }

        String replyStr;
        try {
            replyStr = new String(reply, 0, replyLen, "ASCII7");
        } catch (UnsupportedEncodingException ignored) {
            replyStr = new String(reply, 0, replyLen);
        }

        // Some proxies return http/1.1, some http/1.0 even we asked for 1.0
        if (!replyStr.startsWith("HTTP/1.0 200") && !replyStr.startsWith("HTTP/1.1 200")) {
            throw new IOException("Unable to tunnel. Proxy returns \"" + replyStr + "\"");
        }
        SSLSocket s = (SSLSocket) ((SSLSocketFactory) SSLSocketFactory.getDefault())
                .createSocket(socket, url.getHost(), url.getPort(), true);
        s.startHandshake();
        socket = s;
        msg.setLength(0);
        msg.append(method);
        msg.append(" ");
        msg.append(url.toExternalForm().split(String.valueOf(url.getPort()), -2)[1]);
        msg.append(" HTTP/1.0\r\n");
        for (Map.Entry<String, List<String>> h : sendheaders.entrySet()) {
            for (String l : h.getValue()) {
                msg.append(h.getKey()).append(": ").append(l);
                msg.append("\r\n");
            }
        }
        if (method.equals("POST") || method.equals("PUT")) {
            msg.append("Transfer-Encoding: Chunked\r\n");
        }
        msg.append("Host: ").append(url.getHost()).append("\r\n");
        msg.append("Connection: close\r\n");
        msg.append("\r\n");
        try {
            bytes = msg.toString().getBytes("ASCII7");
        } catch (UnsupportedEncodingException ignored) {
            bytes = msg.toString().getBytes();
        }
        socket.getOutputStream().write(bytes);
        socket.getOutputStream().flush();
    }

    private void afterWrite() throws IOException {
        if (afterwritten) {
            return;
        }
        afterwritten = true;
        socket.getOutputStream().write(String.valueOf(0).getBytes());
        socket.getOutputStream().write(NEWLINE);
        socket.getOutputStream().write(NEWLINE);
        byte reply[] = new byte[200];
        byte header[] = new byte[200];
        int replyLen = 0;
        int headerLen = 0;
        int newlinesSeen = 0;
        boolean headerDone = false;
        /* Done on first newline */
        InputStream in = socket.getInputStream();
        while (newlinesSeen < 2) {
            int i = in.read();
            if (i < 0) {
                throw new IOException("Unexpected EOF from remote server");
            }
            if (i == '\n') {
                if (headerDone) {
                    String h = new String(header, 0, headerLen);
                    String[] split = h.split(": ");
                    if (split.length != 1) {
                        headers.computeIfAbsent(split[0], l -> new ArrayList<>()).add(split[1]);
                    }
                }
                headerDone = true;
                ++newlinesSeen;
                headerLen = 0;
            } else if (i != '\r') {
                newlinesSeen = 0;
                if (!headerDone && replyLen < reply.length) {
                    reply[replyLen++] = (byte) i;
                } else if (headerLen < header.length) {
                    header[headerLen++] = (byte) i;
                }
            }
        }

        String replyStr;
        try {
            replyStr = new String(reply, 0, replyLen, "ASCII7");
        } catch (UnsupportedEncodingException ignored) {
            replyStr = new String(reply, 0, replyLen);
        }

        /* We asked for HTTP/1.0, so we should get that back */
        if ((!replyStr.startsWith("HTTP/1.0 200")) && !replyStr.startsWith("HTTP/1.1 200")) {
            throw new IOException("Server returns \"" + replyStr + "\"");
        }
    }

    @Override
    public void disconnect() {
        try {
            socket.close();
        } catch (IOException ex) {
            Logger.getLogger(ProxiedHttpsConnection.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    @Override
    public boolean usingProxy() {
        return true;
    }
}

上面代码的当前错误:

  • 发布期间错误未关闭流
  • 在首次与代理联系时发生错误时,流未关闭
  • 它不支持http重定向
  • 它不支持诸如分块和gzip编码之类的http 1.1,但这并不是问题,因为我们宣布自己为http1.0客户端。

上面的代码可以像这样使用:

    ProxiedHttpsConnection n = new ProxiedHttpsConnection(
            new URL("https://stackoverflow.com:443/questions/3304006/persistent-httpurlconnection-in-java"), 
            "proxy.example.com", 8080, "root", "flg83yvem#");
    n.setRequestMethod("GET");
    n.addRequestProperty("User-Agent", "Java test https://stackoverflow.com/users/1542723/ferrybig");
    //try (OutputStream out = n.getOutputStream()) {
    //  out.write("Hello?".getBytes());
    //}
    try (InputStream in = n.getInputStream()) {
        byte[] buff = new byte[1024];
        int length;
        while ((length = in.read(buff)) >= 0) {
            System.out.write(buff, 0, length);
        }
    }

如果要将它与一种代理选择器一起使用,则应检查url的协议,以查看其http还是https,如果它的http,则不要使用此类,而是手动附加标头,例如:

httpURLConnection.setRequestProperty("Proxy-Authorization", "Basic " + encoded);

为什么不使用httpsUrlConnection.setSSLSocketFactory

虽然Java有此方法,但是尝试使用它会向您展示为什么它不起作用,而Java只会createSocket(Socket s, String host, int port, boolean autoClose)通过已打开的连接不断调用,从而无法手动进行代理操作。

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章

来自分类Dev

ISP上的Web代理身份验证

来自分类Dev

Apache HttpClient 4.3.1中具有HTTP隧道/ HTTPS连接的抢占式代理身份验证

来自分类Dev

oracle代理身份验证和nhibernate

来自分类Dev

使用camel-http进行HTTPS身份验证设置?

来自分类Dev

Web身份验证代理总是失败?

来自分类Dev

使用鱿鱼代理的基本身份验证

来自分类Dev

Java代理身份验证(使用HttpURLConnection)

来自分类Dev

如何使用基本身份验证创建esb代理

来自分类Dev

git svn https身份验证

来自分类Dev

HTTPS和Firebase身份验证

来自分类Dev

WCF服务调用返回“ 407必需的代理身份验证”

来自分类Dev

与代理(和身份验证)的groovy httpBuilder

来自分类Dev

Java中的每代理身份验证

来自分类Dev

通过Https使用基本身份验证的骆驼http4下载文件

来自分类Dev

即使具有适当的凭据,通过cURL与NTLM的代理身份验证也会返回407

来自分类Dev

代理身份验证和节点请求

来自分类Dev

TFS vNext构建代理失败,并显示407代理身份验证错误

来自分类Dev

OkHTTPClient代理身份验证如何?

来自分类Dev

通过HTTPS Java进行基本身份验证

来自分类Dev

如何使用HTTPS(基本身份验证)向Woocommerce API进行身份验证

来自分类Dev

RStudio代理身份验证

来自分类Dev

https基本身份验证性能

来自分类Dev

即使具有适当的凭据,通过cURL与NTLM的代理身份验证也会返回407

来自分类Dev

HTTPS身份验证和会话劫持

来自分类Dev

Android上的HTTPS身份验证

来自分类Dev

API-HttpResponseMessage:需要(407)代理身份验证

来自分类Dev

AngularJS“需要407代理身份验证”

来自分类Dev

HTTPS 或 JWT 进行身份验证?

来自分类Dev

python https身份验证错误?

Related 相关文章

热门标签

归档