给OkHttp Client添加socks代理
Okhttp的使用没有httpClient广泛,网上关于Okhttp设置代理的方法很少,这篇文章完整介绍了需要注意的方方面面。
上一篇博客中介绍了socks代理的入口是创建java.net.Socket
时传入一个java.net.Porxy
对象。 OkHttp client通过OkHttpClient.Builder
创建,可以通过定制javax.net.ssl.SSLSocketFactory
和java.net.SocketFactory
来实现socks代理。
定制SocketFactory
JDK中默认的是java.net.DefaultSocketFactory
,它是包可见的,没法扩展,所以只能用java.net.SocketFactory
扩展,没有什么其他好的招数,这个过程其实有点繁琐。
public class ProxySocketFactory extends SocketFactory {private ProxyConfigProvider proxyConfigProvider;public ProxySocketFactory(ProxyConfigProvider proxyConfigProvider) {this.proxyConfigProvider = proxyConfigProvider;}@Overridepublic Socket createSocket() throws IOException {ProxyConfig proxyConfig = proxyConfigProvider.getProxyConfig();if (proxyConfig != null) {return new Socket(proxyConfig.getProxy());} else {return new Socket();}}public Socket createSocket(String host, int port)throws IOException, UnknownHostException {Socket socket = createSocket();try {socket.connect(new InetSocketAddress(host, port));} catch (IOException e) {socket.close();throw e;}return socket;}public Socket createSocket(InetAddress address, int port)throws IOException {Socket socket = createSocket();try {socket.connect(new InetSocketAddress(address, port));} catch (IOException e) {socket.close();throw e;}return socket;}public Socket createSocket(String host, int port,InetAddress clientAddress, int clientPort)throws IOException, UnknownHostException {Socket socket = createSocket();try {socket.bind(new InetSocketAddress(clientAddress, clientPort));socket.connect(new InetSocketAddress(host, port));} catch (IOException e) {socket.close();throw e;}return socket;}public Socket createSocket(InetAddress address, int port,InetAddress clientAddress, int clientPort)throws IOException {Socket socket = createSocket();try {socket.bind(new InetSocketAddress(clientAddress, clientPort));socket.connect(new InetSocketAddress(address, port));} catch (IOException e) {socket.close();throw e;}return socket;}
}
上面代码这么长,并不是随便写的,也省不了。我参考了DefaultSocketFactory
里面创建Socket
对象的各个构造函数,保证了ProxySocketFactory
的跟DefaultSocketFactory
对应方法的行为完全一致,只是在创建socket时用了代理而已。简单一句话:只要传入了IP地址和端口,就会直接发起连接。
对SSL socket工厂的定制同样繁琐。不是简单的继承(继承搞不定),而是采用包装模式,用原来的SSLSocketFactory
来实现与代理无关的方法。
public class ProxySSLSocketFactory extends SSLSocketFactory {private ProxyConfigProvider configProvider;private SSLSocketFactory socketFactory;public ProxySSLSocketFactory(ProxyConfigProvider configProvider, SSLSocketFactory socketFactory) {this.configProvider = configProvider;this.socketFactory = socketFactory;}@Overridepublic String[] getDefaultCipherSuites() {return socketFactory.getDefaultCipherSuites();}@Overridepublic String[] getSupportedCipherSuites() {return socketFactory.getSupportedCipherSuites();}public Socket createSocket()throws IOException {ProxyConfig proxyConfig = configProvider.getProxyConfig();if (proxyConfig != null) {return new Socket(proxyConfig.getProxy());} else {return new Socket();}}public Socket createSocket(String host, int port)throws IOException {Socket socket = createSocket();try {return socketFactory.createSocket(socket, host, port, true);} catch (IOException e) {socket.close();throw e;}}public Socket createSocket(Socket s, String host,int port, boolean autoClose)throws IOException {//TODO 无法代理return socketFactory.createSocket(s, host, port, autoClose);}public Socket createSocket(InetAddress address, int port)throws IOException {Socket socket = createSocket();try {return socketFactory.createSocket(socket, address.getHostAddress(), port, true);} catch (IOException e) {socket.close();throw e;}}public Socket createSocket(String host, int port,InetAddress clientAddress, int clientPort)throws IOException {Socket socket = createSocket();try {socket.bind(new InetSocketAddress(clientAddress, clientPort));return socketFactory.createSocket(socket, host, port, true);} catch (IOException e) {socket.close();throw e;}}public Socket createSocket(InetAddress address, int port,InetAddress clientAddress, int clientPort)throws IOException {Socket socket = createSocket();try {socket.bind(new InetSocketAddress(clientAddress, clientPort));return socketFactory.createSocket(socket, address.getHostAddress(), port, true);} catch (IOException e) {socket.close();throw e;}}
}
注意,其中有一个方法是无法使用代理的:Socket createSocket(Socket s, String host,int port, boolean autoClose)
,因为传入的已经是一个创建好的Socket,所以使用这个方法,要注意你的组件有没有用到这个方法,如果用到了,代理的功能需要在这个方法的调用者那一层去实现。我测试OkHttp client是没有用到这个方法的。
创建OkHttpClient对象
两个工厂类设计好了之后,下面就是运用他们创建OkHttpClient对象。
public OkHttpClient buildOkHttpClient() {OkHttpClient httpClient = new OkHttpClient.Builder().sslSocketFactory(new ProxySSLSocketFactory(proxyProvider, NOP_TLSV12_SSL_CONTEXT.getSocketFactory()), NOP_TRUST_MANAGER).socketFactory(new ProxySocketFactory(proxyProvider)).hostnameVerifier(NOP_HOSTNAME_VERIFIER).connectTimeout(DEFAULT_CONNECTION_TIMEOUT, TimeUnit.SECONDS).writeTimeout(DEFAULT_CONNECTION_TIMEOUT, TimeUnit.SECONDS).readTimeout(DEFAULT_CONNECTION_TIMEOUT, TimeUnit.SECONDS) .addInterceptor(new OkHttpProxyInterceptor()).build();return httpClient;}
上面的方法注意两行:
.sslSocketFactory(xxx)
.socketFactory(xxx)
其他代码,有对超时的设置.xxxTimeout()
,这里不赘述,还有对https请求证书的设置以及对socks密码的设置,稍后详解
设置https需要注意的地方
NOP_TRUST_MANAGER
对象设置为信任任何证书:
public static final X509TrustManager NOP_TRUST_MANAGER = new X509TrustManager() {@Overridepublic void checkClientTrusted(X509Certificate[] x509Certificates, String s)throws CertificateException {}@Overridepublic void checkServerTrusted(X509Certificate[] x509Certificates, String s)throws CertificateException {}@Overridepublic X509Certificate[] getAcceptedIssuers() {return new X509Certificate[0];}};
NOP_HOSTNAME_VERIFIER
对象设置为信任任何域名:
public static final HostnameVerifier NOP_HOSTNAME_VERIFIER = new HostnameVerifier() {@Overridepublic boolean verify(String s, SSLSession sslSession) {return true;}};
https的协议版本可能不支持TLSv1
上面两条设置网上很多文档都会介绍,重点需要关注的是NOP_TLSV12_SSL_CONTEXT
对象。
干货来了:JDK使用的HTTPS协议版本参考这里-diagnosing-tls,-ssl,-and-https,可以看到在JDK7以前,默认都是TLSv1,JDK8才默认采用TLSv1.2,而很多较新的网站都已经禁用HTTPS使用TLSv1握手了,认为这个协议已经不够安全(例如,我在给minio-java client添加proxy支持时,发现minio-server就这么干的)。所以你用java去发起HTTPS连接经常会出现SSL相关的异常,大部分异常信息根本看不懂。
所以,需要在代码里面指定https使用TLSv1.2:
static {try {NOP_TLSV12_SSL_CONTEXT = SSLContext.getInstance("TLSv1.2");NOP_TLSV12_SSL_CONTEXT.init(null, new TrustManager[]{NOP_TRUST_MANAGER}, new java.security.SecureRandom());} catch (NoSuchAlgorithmException e) {e.printStackTrace();} catch (KeyManagementException e) {e.printStackTrace();}}
实际上也可以通过:
System.setProperty("https.protocols","TLSv1.2");//在创建任何SSLSocketFactory之前调用
或者设置虚拟机参数:
-Dhttps.protocols=TLSv1.2,TLSv1.1,TLSv1
放在前面的协议会被优先选用
检测https支持的协议版本
如果出现SSL相关异常,但是又不确定是不是协议版本导致的,可以有几个工具进行检验:
nmap : nmap --script ssl-enum-ciphers -p 443 baidu.com
,在我机器上看到:
PORT STATE SERVICE
443/tcp open https
| ssl-enum-ciphers:
| SSLv3:
| ciphers:
| TLS_ECDHE_RSA_WITH_RC4_128_SHA (secp256r1) - A
| TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA (secp256r1) - A
| TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA (secp256r1) - A
| TLS_RSA_WITH_AES_128_CBC_SHA (rsa 2048) - A
| TLS_RSA_WITH_AES_256_CBC_SHA (rsa 2048) - A
| TLS_RSA_WITH_RC4_128_SHA (rsa 2048) - A
| compressors:
| NULL
| cipher preference: server
| warnings:
| CBC-mode cipher in SSLv3 (CVE-2014-3566)
| TLSv1.0:
| ciphers:
| TLS_ECDHE_RSA_WITH_RC4_128_SHA (secp256r1) - A
| TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA (secp256r1) - A
| TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA (secp256r1) - A
| TLS_RSA_WITH_AES_128_CBC_SHA (rsa 2048) - A
| TLS_RSA_WITH_AES_256_CBC_SHA (rsa 2048) - A
| TLS_RSA_WITH_RC4_128_SHA (rsa 2048) - A
| compressors:
| NULL
| cipher preference: server
| TLSv1.1:
| ciphers:
| TLS_ECDHE_RSA_WITH_RC4_128_SHA (secp256r1) - A
| TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA (secp256r1) - A
| TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA (secp256r1) - A
| TLS_RSA_WITH_AES_128_CBC_SHA (rsa 2048) - A
| TLS_RSA_WITH_AES_256_CBC_SHA (rsa 2048) - A
| TLS_RSA_WITH_RC4_128_SHA (rsa 2048) - A
| compressors:
| NULL
| cipher preference: server
| warnings:
| Weak cipher RC4 in TLSv1.1 or newer not needed for BEAST mitigation
| TLSv1.2:
| ciphers:
| TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (secp256r1) - A
| TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 (secp256r1) - A
| TLS_ECDHE_RSA_WITH_RC4_128_SHA (secp256r1) - A
| TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA (secp256r1) - A
| TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA (secp256r1) - A
| TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (secp256r1) - A
| TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 (secp256r1) - A
| TLS_RSA_WITH_AES_128_GCM_SHA256 (rsa 2048) - A
| TLS_RSA_WITH_AES_256_GCM_SHA384 (rsa 2048) - A
| TLS_RSA_WITH_AES_128_CBC_SHA256 (rsa 2048) - A
| TLS_RSA_WITH_AES_128_CBC_SHA (rsa 2048) - A
| TLS_RSA_WITH_AES_256_CBC_SHA256 (rsa 2048) - A
| TLS_RSA_WITH_AES_256_CBC_SHA (rsa 2048) - A
| TLS_RSA_WITH_RC4_128_SHA (rsa 2048) - A
| compressors:
| NULL
| cipher preference: server
| warnings:
| Weak cipher RC4 in TLSv1.1 or newer not needed for BEAST mitigation
|_ least strength: A
设置Socks的用户名和密码
OkHttp提供了拦截器的机制okhttp3.Interceptor
,可以定制http发起的流程。
设置密码有两种方式:一种是设置全局的,另一种是按线程隔离(也是就是按请求隔离),不同请求可以设置不同的用户名和密码,详细介绍以及部分代码见我的上一篇博客-给HttpClient添加Socks代理。
注意到上面代码有这么一行:
.addInterceptor(new OkHttpProxyInterceptor())
就是设置一个拦截器。
private class OkHttpProxyInterceptor implements Interceptor {@Overridepublic Response intercept(Chain chain) throws IOException {ProxyConfig proxyConfig = ProxyHttpClientBuilder.this.getProxyConfig();boolean clearCredentials = false;if (proxyConfig != null) {if (proxyConfig.getAuthentication() != null) {ThreadLocalProxyAuthenticator.setCredentials(proxyConfig.getAuthentication());clearCredentials = true;}}try {return chain.proceed(chain.request());} finally {if (clearCredentials) {ThreadLocalProxyAuthenticator.clearCredentials();}}}}
ThreadLocalProxyAuthenticator
,ProxyConfig
等类见上一篇博客。
给OkHttp Client添加socks代理相关推荐
- 给HttpClient添加Socks代理
本文描述http client使用socks代理过程中需要注意的几个方面:1,socks5支持用户密码授权:2,支持https:3,支持让代理服务器解析DNS: 使用代理创建Socket 从原理上来看 ...
- ios设备使用socks代理
在linux上运行了socks5的代理客户端之后,发现我的iphone在局域网的详细信息里竟然无法设置socks代理,只有http代理,而我的代理客户端又不支持http代理,所以找了下iphone上设 ...
- MobaXterm的SOCKS代理连接与bitvise client 软件的C2S与S2C连接
1.MobaXterm的SOCKS代理连接的一种使用场景 远程两台Linux服务器,外网服务器和内网服务器,外网服务器有内外网ip,内网服务器不提供外网ip 外网服务器外网ip为100.100.100 ...
- 使用代理_工具的使用|MSF搭建socks代理
目录 搭建代理 添加路由 搭建Socks4a代理 搭建Socks5代理 连接代理 注:通过MSF起的socks代理,经常性的不监听端口,也就导致代理失败.试过好多次都是这样,应该是MSF的一个bug. ...
- 内网渗透建立代理通道(如何攻击目标内网机器?)-Socks代理(゚益゚メ) 渗透测试
文章目录 搭建靶场 配置虚拟机网络 虚拟机 上线目标1(Target1) Socks代理 简介 正向代理 反向代理 FRP 一层代理 二层代理(多层代理) EW 正向代理 反向代理 二层代理流量转发 ...
- 使用ssh正向连接、反向连接、做socks代理的方法
文章出处:http://dzmailbox.blog.163.com/blog/static/120534385201232642637847/ 最近才发现ssh有多么的强大! 在网上搜了半天,发现大 ...
- 内网安全学习(六)—域横向-内网漫游: Socks 代理
内网安全-域横向内网漫游 Socks 代理隧道技术 1.前置知识: 1)正向与反向连接: 正向就是你去连接被控主机,但由于机器处于内网内,分配的内网ip,无法直接找到,所以需要方向连接,即让主机连接我 ...
- 反弹socks代理。
在内网渗透中,反弹socks代理是很必要的,大家都知道用lcx来转发端口,好像很少看到有人是直接反弹代理来连接.因为我们要连接内网的其 它机器,我们不可能一个一个的去中转端口连接,在当前控制的机器上开 ...
- HttpClient 实现 socks 代理
httpclient 实现 socks 代理 使用的环境 <dependency><groupId>org.apache.httpcomponents</groupId& ...
最新文章
- 第三方工具生成密钥对连接GCP服务器(putty生成密钥远程连接服务器)
- 初识HTML流水笔记
- RRT,RRT*,A*,Dijkstra,PRM算法
- 依赖注入的三种方式_一起学Spring之三种注入方式及集合类型注入
- python批量写入数据库engine_python 快速写入postgresql数据库方法
- Java学习优秀网站
- SVM之交叉验证【转】
- 清除nginx服务器网站缓存数据
- defaultdict python3,Python collections.defaultdict() 与 dict的使用和区别|python3教程|python入门|python教程...
- Java学习之JDK的安装与配置
- java process 重启_JAVA Process启动sh 后的问题
- 五、扩展Orchard(五) Writing a Content Part
- vue页面无操作10分钟内调转到登录页面
- java 串口通信问题_jsp,java串口通信的问题
- 成年人の内部 福利 不敢高调分享……
- 2018-2019-1 20165232 20165231 20165235实验二——固件程序设计
- layui扩展模块的使用注意事项
- 运算放大器stb仿真与闭环增益备忘
- 常见未授权访问漏洞详解
- 随机论---生命起源随想
热门文章
- vue实现搜索框搜索新增_基于Vue.js实现简单搜索框
- 趣店再次收到不合规通知函:市值不足2亿美元 面临退市危机
- CO2/MAG/MIG焊接机(碳钢和不锈钢脉冲)
- 2020.4.15华为实习招聘笔试题第三题
- python exception in thread_这个是什么原因,请问怎么处理Exception in thr
- JavaScript note
- 轴承故障诊断分类中常用的一些数据集介绍和获取方法
- 2. Hadoop的安装(这你都没装好,我就服了)
- 各种activation function(激活函数) 简介
- 创意APP如何盈利?怎么赚钱的?