Joh*_*ohn 19 java authentication proxy basic-authentication http-proxy
我想在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();
Run Code Online (Sandbox Code Playgroud)
但是,该代码不适用于HTTPS网址(例如" 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();
Run Code Online (Sandbox Code Playgroud)
第二个代码的问题是它设置了一个新的默认值Authenticator,我不想这样做,因为这个代理仅由应用程序的一部分使用,而应用程序的不同部分可能使用不同的代理.我不想为整个应用程序设置全局默认值.有没有办法让第一个代码使用HTTPS或使用一种方法Authenticator而不将其设置为默认值?
我必须使用java.net.HttpURLConnection,因为我重写了一个必须返回一个类的方法HttpURLConnection,所以我不能使用Apache HttpClient.
您可以自己扩展ProxiedHttpsConnection和处理所有低级相关的东西。
要通过 HTTP 代理连接到 https 网站,需要执行以下步骤:
注意:与代理和 http 服务器的通信应该是ASCII7。
CONNECT stackoverflow.com:443 HTTP/1.0\r\n给代理Proxy-Authorization: Basic c2F5WW91SGF2ZVNlZW5UaGlzSW5UaGVDb21tZW50cw==\r\n。\r\nHTTP/1.0 200.GET /questions/3304006/persistent-httpurlconnection-in-java HTTP/1.0\r\nHost: stackoverflow.com\r\n\r\n\r\n并将第一行解析为状态消息当我们要实现 HttpUrlConnection 类时,还需要考虑以下几点:
OutputStream意味着数据传输完成,而不是连接必须完成快说,只是有很多陷阱
在我设计的类中,它使用布尔标志来记住connect方法和afterPostClosure方法是否被调用,它还支持 ifgetInputStream()在OutputStream关闭之前被调用。
此类还在套接字返回的流上使用尽可能少的包装,以防止变得非常复杂。
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;
}
}
Run Code Online (Sandbox Code Playgroud)
上述代码的当前错误:
上面的代码可以像这样使用:
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 /sf/users/107990641/");
//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);
}
}
Run Code Online (Sandbox Code Playgroud)
如果您打算将它与一种代理选择器一起使用,您应该检查 url 的协议,看看它是 http 还是 https,如果是 http,不要使用此类,而是手动附加标头,如:
httpURLConnection.setRequestProperty("Proxy-Authorization", "Basic " + encoded);
Run Code Online (Sandbox Code Playgroud)
虽然 java 有这个方法,尝试使用它会告诉你为什么它不起作用,java 只是createSocket(Socket s, String host, int port, boolean autoClose)用一个已经打开的连接调用,因此无法手动执行代理操作。
小智 6
不幸的是,没有简单的解决方案可以实现您的目标.您的第一个代码不适用于HTTPS,因为您直接设置了身份验证标头.由于客户端加密所有数据,因此代理服务器无法从请求中提取任何信息.
事实上,HTTPS和代理服务器以相反的方式工作.代理服务器希望查看在客户端和最终服务器之间流动的所有数据,并根据其看到的内容采取措施.另一方面,HTTPS协议对所有数据进行加密,这样在到达最终目的地之前,任何人都无法看到数据.加密算法在客户端和最终目的地之间协商,以便代理服务器不能解密任何信息,实际上它甚至不知道客户端正在使用哪种协议.
要在HTTPS连接上使用代理服务器,客户端必须建立隧道.为此,它必须直接向代理发出CONNECT命令,例如:
CONNECT www.google.com:443 HTTP/1.0
Run Code Online (Sandbox Code Playgroud)
并发送凭据以使用代理服务器进行身份验证.
如果连接成功,则客户端可以通过连接发送和接收数据.代理服务器对数据完全失明.数据仅在客户端和服务器之间的路径上传递.
当您url.openConnection(proxy)在HTTP URL上执行时,它返回一个实例HttpURLConnection,当在HTTPS URL上运行时,就像在第二个代码中一样,它返回一个实例HttpsURLConnection.
您收到407错误代码,因为代理服务器无法从您发送的标头中提取身份验证信息.查看异常堆栈,我们可以看到抛出异常sun.net.www.protocol.http.HttpURLConnection.doTunneling(),发出CONNECT命令以通过代理建立HTTPS隧道.在源代码中sun.net.www.protocol.http.HttpURLConnection我们可以看到:
/* 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;
Run Code Online (Sandbox Code Playgroud)
因此,默认身份验证器似乎是提供代理凭据的唯一方法.
要做你想做的事,你必须自己去连接级别并处理HTTP协议,因为你必须与代理服务器直接对话,不要直接与谷歌服务器通信.
| 归档时间: |
|
| 查看次数: |
19330 次 |
| 最近记录: |