it-swarm.com.de

Bei der Basis-Proxy-Authentifizierung für HTTPS-URLs wird die Proxy-Authentifizierung für HTTP/1.0 407 zurückgegeben

Ich möchte einen Proxy mit Basisauthentifizierung (Benutzername, Kennwort) für eine Verbindung (und nur diese Verbindung) in Java verwenden. Der folgende Code funktioniert für HTTP-URLs (z. B. " 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();

Der Code funktioniert jedoch nicht für HTTPS-URLs (z. B. " https://www.google.com ")! Ich bekomme Java.io.IOException: Unable to tunnel through proxy. Proxy returns "HTTP/1.0 407 Proxy Authentication Required", wenn ich versuche, auf eine HTTPS-URL zuzugreifen.

Dieser Code funktioniert für HTTP und 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();

Das Problem mit dem zweiten Code ist, dass er einen neuen Standardwert Authenticator setzt. Ich möchte das nicht tun, da dieser Proxy nur von einem Teil der Anwendung verwendet wird und ein anderer Teil der Anwendung möglicherweise einen anderen Proxy verwendet. Ich möchte keinen globalen Standard für die gesamte Anwendung festlegen. Gibt es eine Möglichkeit, den ersten Code mit HTTPS zu arbeiten, oder eine Authenticator verwenden, ohne sie als Standard festzulegen?

Ich muss Java.net.HttpURLConnection verwenden, da ich eine Methode einer Klasse überschreibe, die eine HttpURLConnection zurückgeben muss, sodass ich Apache HttpClient nicht verwenden kann.

18
John

Sie können ProxiedHttpsConnection erweitern und alle damit zusammenhängenden Dinge selbst erledigen.

Die folgenden Schritte müssen ausgeführt werden, um eine Verbindung über einen HTTP-Proxy zu einer https-Website herzustellen:

Hinweis: Die Kommunikation mit dem Proxy- und http-Server sollte in ASCII7 erfolgen.

  1. Senden Sie CONNECT stackoverflow.com:443 HTTP/1.0\r\n an den Proxy
  2. Senden Sie Ihre Authentifizierung: Proxy-Authorization: Basic c2F5WW91SGF2ZVNlZW5UaGlzSW5UaGVDb21tZW50cw==\r\n.
  3. Beenden Sie die erste Anforderung: \r\n
  4. Lesen Sie die Antwort vom Proxy, bis Sie die Kombination "\ r\n\r\n" sehen.
  5. Analysieren Sie die erste Zeile der Antwort, die Sie vom Proxy erhalten haben, und prüfen Sie, ob sie mit HTTP/1.0 200 beginnt.
  6. Starten Sie eine SSL-Sitzung über die vorhandene Verbindung.
  7. Senden Sie den Start einer http-Anfrage: GET /questions/3304006/persistent-httpurlconnection-in-Java HTTP/1.0\r\n
  8. Legen Sie den richtigen Host-Header fest: Host: stackoverflow.com\r\n
  9. Beenden Sie die Anfrage an den http-Server: \r\n
  10. Lesen Sie bis \r\n und parsen Sie die erste Zeile als Statusmeldung
  11. Lesen Sie bis zum Ende des Streams den Anfragetext

Wenn wir die HttpUrlConnection-Klasse implementieren möchten, müssen wir ein paar Dinge beachten:

  • Zum Zeitpunkt der Erstellung der Klasse sollte die Klasse Daten für zukünftige Verbindungen speichern, jedoch NICHT direkt erstellen
  • Jede Methode kann in beliebiger Reihenfolge aufgerufen werden
  • Das Schließen von OutputStream bedeutet, dass die Datenübertragung abgeschlossen ist und nicht, dass die Verbindung beendet werden muss
  • Jede API verwendet die Methoden in einer anderen Reihenfolge
  • Bei HTTP-Headern wird die Groß- und Kleinschreibung nicht berücksichtigt, bei Java-Maps wird die Groß- und Kleinschreibung beachtet.

Schnell gesagt, es gibt nur viele Fallstricke

In der von mir entworfenen Klasse verwendet es boolesche Flags, um sich zu erinnern, ob die Methoden connect und afterPostClosure aufgerufen werden. Außerdem wird unterstützt, wenn getInputStream() aufgerufen wird, bevor der OutputStream geschlossen wird. 

Diese Klasse verwendet auch so wenig Wrap wie möglich für die vom Socket zurückgegebenen Streams, um zu verhindern, dass sie wirklich komplex sind.

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;
    }
}

Aktuelle Fehler mit dem obigen Code:

  • Streams werden bei Fehlern während des Postens nicht geschlossen
  • Streams werden bei Fehlern beim ersten Kontakt mit dem Proxy nicht geschlossen
  • Es werden keine http-Weiterleitungen unterstützt
  • Es unterstützt nicht die HTTP-1.1-Funktionen wie Chunked- und Gzip-Codierung. Dies ist jedoch kein Problem, da wir uns als http1.0-Client anmelden.

Der obige Code kann wie folgt verwendet werden:

    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);
        }
    }

Wenn Sie dies mit einer Art Proxy-Selector verwenden möchten, sollten Sie das Protokoll der URL überprüfen, um festzustellen, ob http oder https (falls vorhanden) diese Klasse nicht verwenden, sondern den Header manuell wie folgt anhängen:

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

Warum nicht httpsUrlConnection.setSSLSocketFactory verwenden?

Während Java diese Methode hat, zeigen Versuche, es zu verwenden, warum es nicht funktioniert. Java ruft einfach die createSocket(Socket s, String Host, int port, boolean autoClose) mit einer bereits offenen Verbindung auf, wodurch es unmöglich wird, den Proxy-Vorgang manuell auszuführen.

3
Ferrybig

Leider gibt es keine einfache Lösung für das, was Sie erreichen wollen. Ihr 1. Code funktioniert nicht mit HTTPS, da Sie den Authentifizierungsheader direkt festlegen. Da der Client alle Daten verschlüsselt, kann der Proxy-Server keine Informationen aus der Anforderung extrahieren.

In der Tat arbeiten HTTPS- und Proxyserver gegensätzlich. Der Proxyserver möchte alle Daten sehen, die zwischen dem Client und dem endgültigen Server übertragen werden, und auf der Grundlage dessen, was er sieht, Maßnahmen ergreifen. Andererseits verschlüsselt das HTTPS-Protokoll alle Daten, sodass niemand die Daten sehen kann, bis sie das endgültige Ziel erreichen. Der Verschlüsselungsalgorithmus wird zwischen dem Client und dem endgültigen Ziel ausgehandelt, sodass der Proxyserver keine Informationen entschlüsseln kann. Er kann auch nicht wissen, welches Protokoll der Client verwendet.

Um einen Proxyserver für eine HTTPS-Verbindung zu verwenden, muss der Client einen Tunnel einrichten. Dazu muss ein CONNECT-Befehl direkt an den Proxy gesendet werden. Beispiel:

CONNECT www.google.com:443 HTTP/1.0

und senden Sie die Anmeldeinformationen, um sich beim Proxy-Server zu authentifizieren.

Wenn die Verbindung erfolgreich ist, kann der Client Daten über die Verbindung senden und empfangen. Der Proxy-Server ist für die Daten vollständig blind. Die Daten werden nur auf dem Weg zwischen Client und Server durchlaufen.

Wenn Sie url.openConnection(proxy) für eine HTTP-URL ausführen, wird eine Instanz von HttpURLConnection zurückgegeben. Wenn sie unter einer HTTPS-URL wie in Ihrem zweiten Code ausgeführt wird, führt sie eine Instanz von HttpsURLConnection aus.

Sie erhalten den 407-Fehlercode, da der Proxyserver die Authentifizierungsinformationen nicht aus dem von Ihnen gesendeten Header extrahieren kann. Wenn Sie den Exception-Stack betrachten, sehen Sie, dass die Exception an Sun.net.www.protocol.http.HttpURLConnection.doTunneling() ausgelöst wird, die den CONNECT-Befehl ausgibt, um den HTTPS-Tunnel über den Proxy einzurichten. Im Quellcode für Sun.net.www.protocol.http.HttpURLConnection sehen wir:

/* We only have a single static authenticator for now.
 * REMIND:  backwards compatibility with JDK 1.1.  Should be
 * eliminated for JDK 2.0.
 */
private static HttpAuthenticator defaultAuth;

Es scheint also, dass der Standardauthentifizierer die einzige Möglichkeit ist, die Proxy-Anmeldeinformationen bereitzustellen.

Um das zu tun, was Sie möchten, müssen Sie zur Verbindungsebene gehen und das HTTP-Protokoll selbst handhaben, da Sie mit dem Proxy-Server und nicht direkt mit dem Google-Server sprechen müssen.

6
whbogado

Können Sie HttpsUrlConnection verwenden? Es erweitert HttpUrlConnection, sodass das Umschalten auf HttpUrlConnection in Ordnung sein kann, wenn Sie von der Klasse zurückkehren.

Der Code ist ähnlich, verwenden Sie statt HttpUrlConnection einen mit https im Namen.

Verwenden Sie folgenden Code:

if (testUrlHttps.getProtocol().toLowerCase().equals("https")) {
   trustAllHosts();
   HttpsURLConnection https = (HttpsURLConnection) url.openConnection();
   https.setHostnameVerifier(DO_NOT_VERYFY);
   urlCon = https;
} else {
   urlCon = (HttpURLConnection) url.openConnection();
}

Quellen:

[1] https://docs.Oracle.com/javase/7/docs/api/javax/net/ssl/HttpsURLConnection.html

[2] HTTP-Verbindung - "https: //" vs. "http: //" (Snippet)

2
mico

Ok, das ist was Sie tun müssen,

public class ProxyAuth extends Authenticator {
    private PasswordAuthentication auth;

    ProxyAuth(String user, String password) {
        auth = new PasswordAuthentication(user, password == null ? new char[]{} : password.toCharArray());
    }

    protected PasswordAuthentication getPasswordAuthentication() {
        return auth;
    }
}

.

public class ProxySetup {
    public HttpURLConnection proxySetup(String urlInput)
    {
        URL url;
        try {
            url = new URL(urlInput);

            Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("10.66.182.100", 80)); // or whatever your proxy is
            HttpURLConnection uc = (HttpURLConnection)url.openConnection(proxy);
            System.setProperty("https.proxyHost", "10.66.182.100");
            System.setProperty("https.proxyPort", "80");
            System.setProperty("http.proxyHost", "10.66.182.100");
            System.setProperty("http.proxyPort", "80");
            String encoded = new String(Base64.encodeBase64(("domain\\Username" + ":" + "Password").getBytes()));

            uc.setRequestProperty("Proxy-Authorization", "Basic " + encoded);
            Authenticator.setDefault(new ProxyAuth("domain\\Username", "Password"));

            System.out.println("ProxySetup : proxySetup");
            return uc;
        } catch (Exception e) {
            // TODO Auto-generated catch block
            System.out.println("ProxySetup : proxySetup - Failed");
            e.printStackTrace();
        }
        return null;
    }
}

Verwenden Sie es gerne.

HttpURLConnection conn = new ProxySetup().proxySetup(URL)
0
Abhishek Anand