Java通过代理发起Https请求时丢失请求头导致403错误
问题场景
我们需要通过代理访问防火墙外的HTTPS API,使用HttpURLConnection发起请求时,设置的自定义token请求头丢失,目标服务返回403 Forbidden。
配置代码:
static { System.setProperty("java.net.useSystemProxies", "true"); // 使用Windows系统代理 System.setProperty("javax.net.ssl.trustStore", "trust file"); // SSL信任库路径 System.setProperty("javax.net.ssl.trustStorePassword", "password");// 信任库密码 }
业务代码:
URL obj = new URL(URL); HttpURLConnection con = (HttpURLConnection) obj.openConnection(); con.setRequestMethod("GET"); con.setRequestProperty("token","MYTOKEN"); int responseCode = con.getResponseCode();
问题原因分析
当调用getResponseCode()触发连接时,DelegateHttpsURLConnection的connect()方法会调用plainConnect(),进而进入代理隧道处理的doTunneling()方法。该方法中存在以下逻辑:
MessageHeader savedRequests = requests; requests = new MessageHeader();
这段代码会将用户设置的请求头暂存到临时对象(因为这些头不需要发给代理服务器),但后续完成代理隧道连接后,没有把暂存的请求头恢复到原请求中,导致最终发送给目标服务的请求丢失了自定义头。
此前尝试设置-Djdk.http.auth.tunneling.disabledSchemes="",但该参数仅针对Basic认证场景,无法解决自定义请求头丢失的问题。
解决方案
方案1:手动触发隧道连接后重新设置请求头
在调用getResponseCode()之前,先手动触发连接,再重新设置自定义请求头:
URL obj = new URL(URL); HttpURLConnection con = (HttpURLConnection) obj.openConnection(); // 先触发代理隧道连接 con.connect(); // 重新设置自定义请求头和请求方法 con.setRequestProperty("token","MYTOKEN"); con.setRequestMethod("GET"); int responseCode = con.getResponseCode();
注意:需确保connect()调用后请求尚未发送,部分场景下可能需要配合setDoOutput(true)调整调用顺序。
方案2:手动指定代理替代系统代理
不依赖系统代理的默认处理逻辑,手动设置代理信息:
// 手动指定代理地址和端口 Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("proxy-host", 8080)); URL obj = new URL(URL); HttpURLConnection con = (HttpURLConnection) obj.openConnection(proxy); con.setRequestMethod("GET"); con.setRequestProperty("token","MYTOKEN"); // 若需自定义SSL信任,可在此补充相关配置 int responseCode = con.getResponseCode();
方案3:切换至成熟HTTP客户端库
如果HttpURLConnection的原生问题难以规避,建议切换至Apache HttpClient或OkHttp等第三方库,这类库对代理和请求头的处理更稳定:
OkHttp示例代码:
OkHttpClient client = new OkHttpClient.Builder() .proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress("proxy-host", 8080))) .build(); Request request = new Request.Builder() .url(URL) .addHeader("token", "MYTOKEN") .build(); Response response = client.newCall(request).execute();
补充说明
该问题未被广泛关注的原因:多数场景下开发者使用的是标准认证头(如Authorization),这类头在隧道处理中有特殊保留逻辑;而自定义请求头的场景相对小众,且很多开发者会直接选择更易用的第三方HTTP客户端,因此问题曝光度较低。
内容的提问来源于stack exchange,提问作者El Turbo

