给HttpClient添加Socks代理
本文描述http client使用socks代理过程中需要注意的几个方面:1,socks5支持用户密码授权;2,支持https;3,支持让代理服务器解析DNS;
使用代理创建Socket
从原理上来看,不管用什么http客户端(httpclient,okhttp),最终都要转换到java.net.Socket
的创建上去,看到代码:
package java.net;
public Socket(Proxy proxy) {
...
}
这是JDK中对网络请求使用Socks代理的入口方法。(http代理是在http协议层之上的,不在此文讨论范围之内)。
HttpClient要实现socks代理,就需要塞进去一个Proxy对象,也就是定制两个类:org.apache.http.conn.ssl.SSLConnectionSocketFactory
和org.apache.http.conn.socket.PlainConnectionSocketFactory
,分别对应https和http。
代码如下:
private class SocksSSLConnectionSocketFactory extends SSLConnectionSocketFactory {
public SocksSSLConnectionSocketFactory(SSLContext sslContext, HostnameVerifier hostnameVerifier) {
super(sslContext, hostnameVerifier);
}
@Override
public Socket createSocket(HttpContext context) throws IOException {
ProxyConfig proxyConfig = (ProxyConfig) context.getAttribute(ProxyConfigKey);
if (proxyConfig != null) {//需要代理
return new Socket(proxyConfig.getProxy());
} else {
return super.createSocket(context);
}
}
@Override
public Socket connectSocket(int connectTimeout, Socket socket, HttpHost host, InetSocketAddress remoteAddress,
InetSocketAddress localAddress, HttpContext context) throws IOException {
ProxyConfig proxyConfig = (ProxyConfig) context.getAttribute(ProxyConfigKey);
if (proxyConfig != null) {//make proxy server to resolve host in http url
remoteAddress = InetSocketAddress
.createUnresolved(host.getHostName(), host.getPort());
}
return super.connectSocket(connectTimeout, socket, host, remoteAddress, localAddress, context);
}
}
和
private class SocksSSLConnectionSocketFactory extends SSLConnectionSocketFactory {
public SocksSSLConnectionSocketFactory(SSLContext sslContext, HostnameVerifier hostnameVerifier) {
super(sslContext, hostnameVerifier);
}
@Override
public Socket createSocket(HttpContext context) throws IOException {
ProxyConfig proxyConfig = (ProxyConfig) context.getAttribute(ProxyConfigKey);
if (proxyConfig != null) {
return new Socket(proxyConfig.getProxy());
} else {
return super.createSocket(context);
}
}
@Override
public Socket connectSocket(int connectTimeout, Socket socket, HttpHost host, InetSocketAddress remoteAddress,
InetSocketAddress localAddress, HttpContext context) throws IOException {
ProxyConfig proxyConfig = (ProxyConfig) context.getAttribute(ProxyConfigKey);
if (proxyConfig != null) {//make proxy server to resolve host in http url
remoteAddress = InetSocketAddress
.createUnresolved(host.getHostName(), host.getPort());
}
return super.connectSocket(connectTimeout, socket, host, remoteAddress, localAddress, context);
}
}
然后在创建httpclient对象时,给HttpClientConnectionManager设置socketFactoryRegistry
Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
.register(Protocol.HTTP.toString(), new SocksConnectionSocketFactory())
.register(Protocol.HTTPS.toString(), new SocksSSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE))
.build();
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
让代理服务器解析域名
场景:运行httpClient的进程所在主机可能并不能上公网,大部分时候,也无法进行DNS解析,这时通常会出现域名无法解析的IO异常,下面介绍怎么避免在客户端解析域名。
上面有一行代码非常关键:
remoteAddress = InetSocketAddress
.createUnresolved(host.getHostName(), host.getPort());
变量host
是你发起http请求的目标主机和端口信息,这里创建了一个未解析(Unresolved)的SocketAddress,在socks协议握手阶段,InetSocketAddress信息会原封不动的发送到代理服务器,由代理服务器解析出具体的IP地址。
Socks的协议描述中有个片段:
The SOCKS request is formed as follows:
+----+-----+-------+------+----------+----------+
|VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
+----+-----+-------+------+----------+----------+
| 1 | 1 | X'00' | 1 | Variable | 2 |
+----+-----+-------+------+----------+----------+
Where:
o VER protocol version: X'05'
o CMD
o CONNECT X'01'
o BIND X'02'
o UDP ASSOCIATE X'03'
o RSV RESERVED
o ATYP address type of following address
o IP V4 address: X'01'
o DOMAINNAME: X'03'
o IP V6 address: X'04'
代码按上面方法写,协议握手发送的是ATYP=X'03'
,即采用域名的地址类型。否则,HttpClient会尝试在客户端解析,然后发送ATYP=X'01'
进行协商。当然,大多数时候HttpClient在解析域名的时候就挂了。
https中需要注意的问题
在使用httpclient访问https网站的时候,经常会遇到javax.net.ssl包中的异常,例如:
Caused by: javax.net.ssl.SSLException: Received fatal alert: internal_error
at sun.security.ssl.Alerts.getSSLException(Unknown Source) ~[na:1.7.0_80]
at sun.security.ssl.Alerts.getSSLException(Unknown Source) ~[na:1.7.0_80]
一般需要做几个设置:
创建不校验证书链的SSLContext
SSLContext sslContext = null;
try {
sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
@Override
public boolean isTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
return true;
}
}).build();
} catch (Exception e) {
throw new com.aliyun.oss.ClientException(e.getMessage());
}
...
new SocksSSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE)
创建不校验域名的HostnameVerifier
public class NoopHostnameVerifier implements javax.net.ssl.HostnameVerifier {
public static final NoopHostnameVerifier INSTANCE = new NoopHostnameVerifier();
@Override
public boolean verify(final String s, final SSLSession sslSession) {
return true;
}
}
如何使用用户密码授权?
java SDK中给Socks代理授权有点特殊,不是按socket来的,而是在系统层面做的全局配置。比如,可以通过下面代码设置一个全局的Authenticator:
Authenticator.setDefault(new MyAuthenticator("userName", "Password"));
...
class MyAuthenticator extends java.net.Authenticator {
private String user ;
private String password ;
public MyAuthenticator(String user, String password) {
this.user = user;
this.password = password;
}
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(user, password.toCharArray());
}
}
这种方法很简单,不过有些不方便的地方,如果你的产品中需要连接不同的Proxy服务器,而他们的用户名密码是不一样的,那么这个方法就不适用了。
基于ThreadLocal的Authenticator
public class ThreadLocalProxyAuthenticator extends Authenticator{
private ThreadLocal<PasswordAuthentication> credentials = null;
private static class SingletonHolder {
private static final ThreadLocalProxyAuthenticator instance = new ThreadLocalProxyAuthenticator();
}
public static final ThreadLocalProxyAuthenticator getInstance() {
return SingletonHolder.instance;
}
public void setCredentials(String user, String password) {
credentials.set(new PasswordAuthentication(user, password.toCharArray()));
}
public static void clearCredentials() {
ThreadLocalProxyAuthenticator authenticator = ThreadLocalProxyAuthenticator.getInstance();
Authenticator.setDefault(authenticator);
authenticator.credentials.set(null);
}
public PasswordAuthentication getPasswordAuthentication() {
return credentials.get();
}
}
这个类意味着,授权信息只会保存到当前调用者的线程中,其他线程的调用者无法访问,在创建Socket的线程中设置密钥和清理密钥,就可以做到授权按照Socket连接进行隔离。Java TheadLocal相关知识本文不赘述。
按连接隔离的授权
class ProxyHttpClient extends CloseableHttpClient{
private CloseableHttpClient httpClient;
public ProxyHttpClient(CloseableHttpClient httpClient){
this.httpClient=httpClient;
}
protected CloseableHttpResponse doExecute(HttpHost target, HttpRequest request, HttpContext context) throws IOException, ClientProtocolException {
ProxyConfig proxyConfig = //这里获取当前连接的代理配置信息
boolean clearCredentials = false;
if (proxyConfig != null) {
if (context == null) {
context = HttpClientContext.create();
}
context.setAttribute(ProxyConfigKey, proxyConfig);
if (proxyConfig.getAuthentication() != null) {
ThreadLocalProxyAuthenticator.setCredentials(proxyConfig.getAuthentication());//设置授权信息
clearCredentials = true;
}
}
try {
return httpClient.execute(target, request, context);
} finally {
if (clearCredentials) {//清理授权信息
ThreadLocalProxyAuthenticator.clearCredentials();
}
}
}
}
另外,线程是可以复用的,因为每次调用完毕后,都清理了授权信息。
这里有个一POJO类ProxyConfig
,保存的是socks代理的IP端口和用户密码信息。
public class ProxyConfig {
private Proxy proxy;
private PasswordAuthentication authentication;
}
给HttpClient添加Socks代理的更多相关文章
- JAVA知识积累 给HttpClient添加Socks代理
本文描述http client使用socks代理过程中需要注意的几个方面:1,socks5支持用户密码授权:2,支持https:3,支持让代理服务器解析DNS: 使用代理创建Socket 从原理上来看 ...
- 给OkHttp Client添加socks代理
Okhttp的使用没有httpClient广泛,网上关于Okhttp设置代理的方法很少,这篇文章完整介绍了需要注意的方方面面. 上一篇博客中介绍了socks代理的入口是创建java.net.Socke ...
- Xshell添加ssh隧道SOCKS代理
Xshell是一个功能强大的终端模拟器,支持SSH,SFTP.TELNET.RLOGIN和SERIAL 下载地址:http://www.netsarang.com/products/xsh_overv ...
- redsocks 将socks代理转换成全局代理
redsocks 需要手动下载编译.前置需求为libevent组件,当然gcc什么的肯定是必须的. 获取源码 git clone https://github.com/darkk/redsocks 安 ...
- linux配置wifi连接并通过ssh代理开启socks代理
1, 命令行配置连接wifi具体我是用的cubieboard2上Debian主机,其中配置wifi的命令行有wpa_cli,具体用法步骤如下.wpa_cli 命令行执行需要root权限,详细用法请见 ...
- 关于双网卡双宽带Http及Socks代理的配置
1.[硬件环境] a, 1台宿主(win7)+几十台虚拟机(xp)(vm10的版本,估计可打开52台以上的虚拟机) b, 双网卡,其中一个网卡通过路由连接电信ADSL,一个直连集线器,可直接连接移动m ...
- 【转载】SOCKS代理:从***到内网漫游
原文:SOCKS代理:从***到内网漫游 本文原创作者:tahf,本文属FreeBuf原创奖励计划,未经许可禁止转载 之前在Freebuf上学习过很多大牛写的关于Tunnel.SOCKS代理.***等 ...
- 内网漫游之SOCKS代理大结局
0×01 引言 在实际渗透过程中,我们成功入侵了目标服务器.接着我们想在本机上通过浏览器或者其他客户端软件访问目标机器内部网络中所开放的端口,比如内网的3389端口.内网网站8080端口等等.传统的方 ...
- 通过SOCKS代理渗透整个内网
https://blog.csdn.net/SouthWind0/article/details/83111044 通过SOCKS代理渗透整个内网 1.背景 经过前期的渗透工作,我们现在已经成功找到了 ...
随机推荐
- 大数据Hadoop学习之搭建Hadoop平台(2.1)
关于大数据,一看就懂,一懂就懵. 一.简介 Hadoop的平台搭建,设置为三种搭建方式,第一种是"单节点安装",这种安装方式最为简单,但是并没有展示出Hadoop的技术优势,适合 ...
- [bzoj4411] [Usaco2016 Feb]Load balancing
先离散化一下(也可以不用 枚举横坐标,用线段树维护两边纵坐标上的节点数. 每次在线段树上二分...(感觉似乎树状数组也行? #include<cstdio> #include<ios ...
- STOI补番队互测#2
Round2轮到我出了>_<(目测总共10人参加 实际共七人) 具体情况: #1: KPM,360 #2:ccz181078,160 #3:child,150 可惜KPM没看到第一题样例里 ...
- Spark环境搭建(上)——基础环境搭建
Spark摘说 Spark的环境搭建涉及三个部分,一是linux系统基础环境搭建,二是Hadoop集群安装,三是Spark集群安装.在这里,主要介绍Spark在Centos系统上的准备工作--linu ...
- Git的简单的基本使用
前言: 接触了Android Studio,自然是知道了Github这个网站,这个网站有许多大神们做的开源库,我们只需要简单地引入就是可以使用到这些开源库从而实现酷炫的效果,最近也是刚接触到Git的使 ...
- PHP 常用的header头部定义汇总
http://www.jb51.net/article/68159.htm
- VIM命令模式与输入模式切换
vi编辑器 vi是UNIX和类UNIX环境下的可用于创建文件的屏幕编辑器.vi有两种工作模式:命令模式和文本输入模式.启动vi需要输入vi,按[Spacebar]键并输入文件名后回车. 切换模式键 ...
- C#中的基元类型、值类型和引用类型
C# 中的基元类型.值类型和引用类型 1. 基元类型(Primitive Type) 编译器直接支持的类型称为基元类型.基元类型可以直接映射到 FCL 中存在的类型.例如,int a = 10 中的 ...
- 开地址哈希表(Hash Table)的原理描述与冲突解决
在开地址哈希表中,元素存放在表本身中.这对于某些依赖固定大小表的应用来说非常有用.因为不像链式哈希表在每个槽位上有一个"桶"来存储冲突的元素,所以开地址哈希表需要通过另一种方法来解 ...
- hive(II)--sql考查的高频问题
在了解别人hive能力水平的时候,不管是别人问我还是我了解别人,有一些都是必然会问的东西.问的问题也大都大同小异.这里总结一下我遇到的那些hive方面面试可能涉及的问题 1.行转列(列转行) 当我们建 ...