Okhttp的使用没有httpClient广泛,网上关于Okhttp设置代理的方法很少,这篇文章完整介绍了需要注意的方方面面。

上一篇博客中介绍了socks代理的入口是创建java.net.Socket时传入一个java.net.Porxy对象。 OkHttp client通过OkHttpClient.Builder创建,可以通过定制javax.net.ssl.SSLSocketFactoryjava.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代理相关推荐

  1. 给HttpClient添加Socks代理

    本文描述http client使用socks代理过程中需要注意的几个方面:1,socks5支持用户密码授权:2,支持https:3,支持让代理服务器解析DNS: 使用代理创建Socket 从原理上来看 ...

  2. ios设备使用socks代理

    在linux上运行了socks5的代理客户端之后,发现我的iphone在局域网的详细信息里竟然无法设置socks代理,只有http代理,而我的代理客户端又不支持http代理,所以找了下iphone上设 ...

  3. MobaXterm的SOCKS代理连接与bitvise client 软件的C2S与S2C连接

    1.MobaXterm的SOCKS代理连接的一种使用场景 远程两台Linux服务器,外网服务器和内网服务器,外网服务器有内外网ip,内网服务器不提供外网ip 外网服务器外网ip为100.100.100 ...

  4. 使用代理_工具的使用|MSF搭建socks代理

    目录 搭建代理 添加路由 搭建Socks4a代理 搭建Socks5代理 连接代理 注:通过MSF起的socks代理,经常性的不监听端口,也就导致代理失败.试过好多次都是这样,应该是MSF的一个bug. ...

  5. 内网渗透建立代理通道(如何攻击目标内网机器?)-Socks代理(゚益゚メ) 渗透测试

    文章目录 搭建靶场 配置虚拟机网络 虚拟机 上线目标1(Target1) Socks代理 简介 正向代理 反向代理 FRP 一层代理 二层代理(多层代理) EW 正向代理 反向代理 二层代理流量转发 ...

  6. 使用ssh正向连接、反向连接、做socks代理的方法

    文章出处:http://dzmailbox.blog.163.com/blog/static/120534385201232642637847/ 最近才发现ssh有多么的强大! 在网上搜了半天,发现大 ...

  7. 内网安全学习(六)—域横向-内网漫游: Socks 代理

    内网安全-域横向内网漫游 Socks 代理隧道技术 1.前置知识: 1)正向与反向连接: 正向就是你去连接被控主机,但由于机器处于内网内,分配的内网ip,无法直接找到,所以需要方向连接,即让主机连接我 ...

  8. 反弹socks代理。

    在内网渗透中,反弹socks代理是很必要的,大家都知道用lcx来转发端口,好像很少看到有人是直接反弹代理来连接.因为我们要连接内网的其 它机器,我们不可能一个一个的去中转端口连接,在当前控制的机器上开 ...

  9. HttpClient 实现 socks 代理

    httpclient 实现 socks 代理 使用的环境 <dependency><groupId>org.apache.httpcomponents</groupId& ...

最新文章

  1. 第三方工具生成密钥对连接GCP服务器(putty生成密钥远程连接服务器)
  2. 初识HTML流水笔记
  3. RRT,RRT*,A*,Dijkstra,PRM算法
  4. 依赖注入的三种方式_一起学Spring之三种注入方式及集合类型注入
  5. python批量写入数据库engine_python 快速写入postgresql数据库方法
  6. Java学习优秀网站
  7. SVM之交叉验证【转】
  8. 清除nginx服务器网站缓存数据
  9. defaultdict python3,Python collections.defaultdict() 与 dict的使用和区别|python3教程|python入门|python教程...
  10. Java学习之JDK的安装与配置
  11. java process 重启_JAVA Process启动sh 后的问题
  12. 五、扩展Orchard(五) Writing a Content Part
  13. vue页面无操作10分钟内调转到登录页面
  14. java 串口通信问题_jsp,java串口通信的问题
  15. 成年人の内部 福利 不敢高调分享……
  16. 2018-2019-1 20165232 20165231 20165235实验二——固件程序设计
  17. layui扩展模块的使用注意事项
  18. 运算放大器stb仿真与闭环增益备忘
  19. 常见未授权访问漏洞详解
  20. 随机论---生命起源随想

热门文章

  1. vue实现搜索框搜索新增_基于Vue.js实现简单搜索框
  2. 趣店再次收到不合规通知函:市值不足2亿美元 面临退市危机
  3. CO2/MAG/MIG焊接机(碳钢和不锈钢脉冲)
  4. 2020.4.15华为实习招聘笔试题第三题
  5. python exception in thread_这个是什么原因,请问怎么处理Exception in thr
  6. JavaScript note
  7. 轴承故障诊断分类中常用的一些数据集介绍和获取方法
  8. 2. Hadoop的安装(这你都没装好,我就服了)
  9. 各种activation function(激活函数) 简介
  10. 创意APP如何盈利?怎么赚钱的?