Java 使用 OkHttp 对接 SOCKS5 代理的一个坑:为什么代理 IP 没生效?
Java 使用 OkHttp 对接 SOCKS5 代理的一个坑:为什么代理 IP 没生效?
背景
项目后端需要对接第三方接口,但第三方配置了 IP 白名单。本地开发环境的公网 IP 不在白名单内,无法直接请求第三方接口。
为了解决本地调试问题,我们在云服务器上搭建了一个 SOCKS5 代理服务。本地 Java 程序请求第三方接口时,通过 SOCKS5 代理转发,这样第三方看到的请求来源就是云服务器 IP。
整体链路如下:
本地 Java 程序
-> SOCKS5 代理服务器
-> 第三方接口
但在测试时发现,使用 OkHttp 配置 SOCKS5 代理后,请求 https://httpbin.org/ip 并没有正确返回代理服务器的出口 IP。
问题代码
一开始的代理工具类大概是这样:
public class ProxyManager {
private static volatile OkHttpClient proxyClient;
private static final Object lock = new Object();
public static OkHttpClient getProxyClient() {
if (proxyClient == null) {
synchronized (lock) {
if (proxyClient == null) {
String proxyHost = "代理服务器IP";
int proxyPort = 10000;
String proxyUser = "代理账号";
String proxyPass = "代理密码";
Proxy proxy = new Proxy(Proxy.Type.SOCKS, new InetSocketAddress(proxyHost, proxyPort));
proxyClient = new OkHttpClient.Builder()
.proxy(proxy)
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build();
}
}
}
return proxyClient;
}
}
表面上看,代理 IP、端口、账号、密码都写了。但实际问题在于:proxyUser 和 proxyPass 只是普通局部变量,并没有被任何地方使用。
也就是说,这段代码只配置了 SOCKS5 代理地址,没有完成 SOCKS5 认证。
核心原因
OkHttp 对代理认证有一个容易踩坑的点:
OkHttpClient.Builder.proxyAuthenticator(...) 主要用于 HTTP 代理认证,不负责 SOCKS5 代理的用户名密码认证。
SOCKS5 的认证通常由 JDK 底层 Socket 处理,需要通过 java.net.Authenticator 注册认证信息。
所以,如果只是这样:
String proxyUser = "代理账号";
String proxyPass = "代理密码";
这两个变量不会自动生效。
正确写法
可以通过 Authenticator.setDefault(...) 注册 SOCKS5 认证信息,并且一定要限制代理地址和端口,避免影响 JVM 内其它网络请求。
package org.springblade.common.tool;
import okhttp3.OkHttpClient;
import java.net.Authenticator;
import java.net.InetSocketAddress;
import java.net.PasswordAuthentication;
import java.net.Proxy;
import java.util.concurrent.TimeUnit;
/**
* SOCKS5代理客户端管理器。
*/
public class ProxyManager {
private static volatile OkHttpClient proxyClient;
private static final Object LOCK = new Object();
private static final String PROXY_HOST = "代理服务器IP";
private static final int PROXY_PORT = 10000;
private static final String PROXY_USER = "代理账号";
private static final String PROXY_PASS = "代理密码";
/**
* 获取代理专用的OkHttpClient。
*/
public static OkHttpClient getProxyClient() {
if (proxyClient == null) {
synchronized (LOCK) {
if (proxyClient == null) {
registerSocksAuthenticator();
Proxy proxy = new Proxy(Proxy.Type.SOCKS, new InetSocketAddress(PROXY_HOST, PROXY_PORT));
proxyClient = new OkHttpClient.Builder()
.proxy(proxy)
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build();
}
}
}
return proxyClient;
}
/**
* 注册SOCKS5代理认证。
*/
private static void registerSocksAuthenticator() {
Authenticator.setDefault(new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
if (RequestorType.PROXY == getRequestorType()
&& PROXY_HOST.equals(getRequestingHost())
&& PROXY_PORT == getRequestingPort()) {
return new PasswordAuthentication(PROXY_USER, PROXY_PASS.toCharArray());
}
return null;
}
});
}
}
测试方式
可以用下面的方式验证代理出口 IP:
@Test
public void testProxyIp() throws IOException {
OkHttpClient client = ProxyManager.getProxyClient();
Request request = new Request.Builder()
.url("https://httpbin.org/ip")
.build();
try (Response response = client.newCall(request).execute()) {
String responseBody = response.body().string();
System.out.println("代理出口IP:" + responseBody);
}
}
如果代理生效,返回的 IP 应该是代理服务器的公网出口 IP,而不是本机公网 IP。
另一个测试坑
如果只是测试代理连通性,不建议用完整的 @SpringBootTest 启动整个 Spring 容器。
因为 Spring 测试上下文可能会先初始化数据库、Redis、定时任务等组件。比如本地 MySQL 没启动时,测试会先失败在数据库连接阶段,还没执行到 OkHttp 请求。
更轻量的方式是写一个普通 JUnit 测试,不加载 Spring 容器:
public class ProxyManagerTest {
@Test
public void testProxyIp() throws IOException {
OkHttpClient client = ProxyManager.getProxyClient();
Request request = new Request.Builder()
.url("https://httpbin.org/ip")
.build();
try (Response response = client.newCall(request).execute()) {
System.out.println(response.body().string());
}
}
}
总结
Java 使用 OkHttp 配置 SOCKS5 代理时,需要注意:
new Proxy(Proxy.Type.SOCKS, ...)只配置代理地址。- SOCKS5 用户名密码不能只定义变量,必须通过 JDK
Authenticator注册。 OkHttp proxyAuthenticator不适合处理 SOCKS5 认证。Authenticator.setDefault(...)是 JVM 全局配置,应按代理 host 和 port 做精确匹配。- 只测试网络代理时,尽量不要启动完整 Spring Boot 上下文。
这个问题的本质不是 OkHttp 没走代理,而是 SOCKS5 代理认证没有真正生效。
更多推荐
所有评论(0)