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;
} @Override
public 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;
} @Override
public String[] getDefaultCipherSuites() {
return socketFactory.getDefaultCipherSuites();
} @Override
public 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() {
@Override
public void checkClientTrusted(X509Certificate[] x509Certificates, String s)
throws CertificateException {
} @Override
public void checkServerTrusted(X509Certificate[] x509Certificates, String s)
throws CertificateException {
} @Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
};

NOP_HOSTNAME_VERIFIER对象设置为信任任何域名:

    public static final HostnameVerifier NOP_HOSTNAME_VERIFIER = new HostnameVerifier() {
@Override
public 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 {
@Override
public 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. JAVA知识积累 给HttpClient添加Socks代理

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

  3. Xshell添加ssh隧道SOCKS代理

    Xshell是一个功能强大的终端模拟器,支持SSH,SFTP.TELNET.RLOGIN和SERIAL 下载地址:http://www.netsarang.com/products/xsh_overv ...

  4. 【转载】SOCKS代理:从***到内网漫游

    原文:SOCKS代理:从***到内网漫游 本文原创作者:tahf,本文属FreeBuf原创奖励计划,未经许可禁止转载 之前在Freebuf上学习过很多大牛写的关于Tunnel.SOCKS代理.***等 ...

  5. redsocks 将socks代理转换成全局代理

    redsocks 需要手动下载编译.前置需求为libevent组件,当然gcc什么的肯定是必须的. 获取源码 git clone https://github.com/darkk/redsocks 安 ...

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

     ssh -L 219.143.16.157:58080:172.21.163.32:8080 用户名@localhost -p 10142  在 219.143.16.157机器执行   将ssh隧 ...

  7. linux配置wifi连接并通过ssh代理开启socks代理

    1, 命令行配置连接wifi具体我是用的cubieboard2上Debian主机,其中配置wifi的命令行有wpa_cli,具体用法步骤如下.wpa_cli 命令行执行需要root权限,详细用法请见 ...

  8. 关于双网卡双宽带Http及Socks代理的配置

    1.[硬件环境] a, 1台宿主(win7)+几十台虚拟机(xp)(vm10的版本,估计可打开52台以上的虚拟机) b, 双网卡,其中一个网卡通过路由连接电信ADSL,一个直连集线器,可直接连接移动m ...

  9. 内网漫游之SOCKS代理大结局

    0×01 引言 在实际渗透过程中,我们成功入侵了目标服务器.接着我们想在本机上通过浏览器或者其他客户端软件访问目标机器内部网络中所开放的端口,比如内网的3389端口.内网网站8080端口等等.传统的方 ...

随机推荐

  1. Alpha冲刺-(9/10)

    Part.1 开篇 队名:彳艮彳亍团队 组长博客:戳我进入 作业博客:班级博客本次作业的链接 Part.2 成员汇报 组员1(组长)柯奇豪 过去两天完成了哪些任务 进一步优化代码,结合自己负责的部分修 ...

  2. Django URLConf 进阶

    Django处理一个请求 项目启动后根据 settings  ROOT_URLCONF 决定项目根URLconf urlpatterns是django.conf.urls.url()实例的一个Pyth ...

  3. STL详解

    STL概貌                                                                                                ...

  4. 腾讯大数据之TDW计算引擎解析——Shuffle

    转自 https://www.csdn.net/article/2014-05-19/2819831-TDW-Shuffle/1 摘要:腾讯分布式数据仓库基于开源软件Hadoop和Hive进行构建,T ...

  5. MySQL 数据类型说明解释

    在 MySQL 中,有三种主要的类型:Text(文本).Number(数字)和 Date/Time(日期/时间)类型. Text 类型: 数据类型 描述 CHAR(size) 保存固定长度的字符串(可 ...

  6. Java 中 & | ^ 运算符的简单使用

    背景 今天碰到了代码中的按位与运算,复习一下,先列一个各个进制数据表. 顺便复习一下十进制转二进制的计算方式: 接下来解释下这三个运算符: & 按位与,都转为二进制的情况下,同为1则为1,否则 ...

  7. Egg中使用egg-mongoose和常用的Mongoose 方法

    Mongoose Mongoose就是一套操作MongoDB数据库的接口,而Egg中有对应的插件egg-mongoose. 安装 $ npm install egg-mongoose --save 配 ...

  8. HoloLens开发手记 - 使用混合现实捕捉 Using mixed reality capture

    HoloLens给了用户混合真实世界和数字世界的体验.混合现实捕捉(MRC)使得你可以保存这种体验到图片或者视频.这使得你可以分享你的体验给其他人,这允许他们能看到和你所看到一样的全息图像.这些视频和 ...

  9. [Node.js与数据库]node-mysql 模块介绍

    [Node.js与数据库]node-mysql 模块介绍   转载至:https://itbilu.com/nodejs/npm/NyPG8LhlW.html#multiple-statement-q ...

  10. web自动化测试---概述

    最早的时候是做功能测试,web都是自己手工点击看看有没问题,没有系统的进行测试,更别说自动化测试了,那是一段不堪回首的往事,就是因为这,很多问题都流出去了,这是作为测试人员的一大失职,痛定思痛,开始学 ...