Je souhaite utiliser un proxy avec une authentification de base (nom d'utilisateur, mot de passe) pour une connexion (et uniquement cette connexion) en Java. Le code suivant fonctionne pour les URL HTTP (par exemple, " 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();
Le code ne fonctionne pas pour les URL HTTPS (par exemple, " https://www.google.com "), cependant! Je reçois Java.io.IOException: Unable to tunnel through proxy. Proxy returns "HTTP/1.0 407 Proxy Authentication Required"
lorsque j'essaie d'accéder à une URL HTTPS.
Ce code fonctionne pour HTTP et 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();
Le problème avec le deuxième code est qu’il définit une nouvelle valeur par défaut Authenticator
et que je ne veux pas le faire, car ce proxy n’est utilisé que par une partie de l’application et une autre partie de l’application pourrait utiliser un proxy différent. Je ne veux pas définir une valeur par défaut globale pour l'ensemble de l'application. Existe-t-il un moyen de faire en sorte que le premier code fonctionne avec HTTPS ou d'utiliser un Authenticator
sans le définir par défaut?
Je dois utiliser Java.net.HttpURLConnection
, car je redéfinis une méthode d'une classe qui doit renvoyer un HttpURLConnection
, je ne peux donc pas utiliser Apache HttpClient.
Vous pouvez étendre ProxiedHttpsConnection
et gérer vous-même tout ce qui concerne les bas niveaux.
Les étapes suivantes doivent être effectuées pour établir une connexion via un proxy HTTP à un site Web https:
Remarque: la communication avec le proxy et le serveur http doit être dans ASCII7 .
CONNECT stackoverflow.com:443 HTTP/1.0\r\n
au proxyProxy-Authorization: Basic c2F5WW91SGF2ZVNlZW5UaGlzSW5UaGVDb21tZW50cw==\r\n
.\r\n
HTTP/1.0 200
.GET /questions/3304006/persistent-httpurlconnection-in-Java HTTP/1.0\r\n
Host: stackoverflow.com\r\n
\r\n
\r\n
et analyser la première ligne en tant que message d'étatLorsque nous voulons implémenter la classe HttpUrlConnection, nous devons également tenir compte de quelques points:
OutputStream
signifie que le transfert de données est terminé et que la connexion ne doit pas se terminerRapidement dit, il y a juste de nombreux pièges
Dans la classe que j'ai conçue, elle utilise des indicateurs booléens pour se rappeler si la méthode connect
et les méthodes afterPostClosure
sont appelées. Elle est également prise en charge si getInputStream()
est appelé avant la fermeture de OutputStream
.
Cette classe utilise également le moins de wrapping possible sur les flux renvoyés par le socket, pour éviter toute complexité.
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;
}
}
Bogues actuels avec le code ci-dessus:
Le code ci-dessus peut être utilisé comme:
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);
}
}
Si vous allez utiliser cela avec une sorte de sélecteur de proxy, vous devriez vérifier le protocole de l'URL pour voir si son http ou https, si c'est http, n'utilisez pas cette classe, et attachez l'en-tête manuellement comme ceci:
httpURLConnection.setRequestProperty("Proxy-Authorization", "Basic " + encoded);
Bien que Java utilise cette méthode, les tentatives d'utilisation vous montreront pourquoi cela ne fonctionne pas, Java continue d'appeler le createSocket(Socket s, String Host, int port, boolean autoClose)
avec une connexion déjà ouverte, rendant impossible la création manuelle du proxy.
Malheureusement, il n’existe pas de solution simple à ce que vous essayez d’atteindre. Votre 1er code ne fonctionne pas avec HTTPS car vous définissez directement l'en-tête d'authentification. Comme le client chiffre toutes les données, le serveur proxy n’a aucun moyen d’extraire des informations de la requête.
En fait, HTTPS et les serveurs proxy fonctionnent de manière opposée. Le serveur proxy veut voir toutes les données qui circulent entre le client et le serveur final et agir en fonction de ce qu'il voit. D'autre part, le protocole HTTPS chiffre toutes les données de sorte que personne ne puisse voir les données avant qu'elles atteignent la destination finale. L'algorithme de chiffrement est négocié entre le client et la destination finale de sorte que le serveur proxy ne puisse déchiffrer aucune information. En fait, il ne sait même pas quel protocole le client utilise.
Pour utiliser un serveur proxy sur une connexion HTTPS, le client doit établir un tunnel. Pour ce faire, il doit émettre une commande CONNECT directement au proxy, par exemple:
CONNECT www.google.com:443 HTTP/1.0
et envoyez les informations d'identification pour vous authentifier auprès du serveur proxy.
Si la connexion est établie, le client peut envoyer et recevoir des données via la connexion. Le serveur proxy est complètement aveugle aux données. Les données ne les transitent que par le chemin entre le client et le serveur.
Lorsque vous exécutez url.openConnection(proxy)
sur une URL HTTP, il retourne une instance de HttpURLConnection
. Lorsqu'il est exécuté sur une URL HTTPS comme dans votre deuxième code, il renvoie une instance de HttpsURLConnection
.
Vous recevez le code d'erreur 407 car le serveur proxy ne peut pas extraire les informations d'authentification de l'en-tête que vous avez envoyé. En examinant la pile d'exceptions, nous pouvons voir que l'exception est levée à Sun.net.www.protocol.http.HttpURLConnection.doTunneling()
qui émet la commande CONNECT pour établir le tunnel HTTPS via le proxy. Dans le code source de Sun.net.www.protocol.http.HttpURLConnection
on peut voir:
/* 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;
Il semble donc que l'authentificateur par défaut est le seul moyen de fournir les informations d'identification du proxy.
Pour faire ce que vous voulez, vous devez descendre au niveau de la connexion et gérer vous-même le protocole HTTP, car vous devez parler au serveur proxy, pas directement au serveur Google.
Pouvez-vous utiliser HttpsUrlConnection? Il étend HttpUrlConnection, le transtypage vers HttpUrlConnection peut donc être correct lors du retour de la classe.
Le code est similaire, à la place de HttpUrlConnection, utilisez-en un avec https dans le nom.
Utilisez le code suivant:
if (testUrlHttps.getProtocol().toLowerCase().equals("https")) {
trustAllHosts();
HttpsURLConnection https = (HttpsURLConnection) url.openConnection();
https.setHostnameVerifier(DO_NOT_VERYFY);
urlCon = https;
} else {
urlCon = (HttpURLConnection) url.openConnection();
}
Sources:
[1] https://docs.Oracle.com/javase/7/docs/api/javax/net/ssl/HttpsURLConnection.html
[2] HttpURLConnection - "https: //" vs. "http: //" (extrait)
Ok c'est ce que vous devez faire,
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;
}
}
Utilisez-le comme.
HttpURLConnection conn = new ProxySetup().proxySetup(URL)