Java 11 HttpClient代理认证异常:getPasswordAuthentication()未触发且始终返回407状态码
Java 11 HttpClient代理认证异常:getPasswordAuthentication()未触发且始终返回407状态码
我之前也碰到过几乎一模一样的问题,折腾了好几天才摸清楚根因,结合你的描述和代码,咱们一步步拆解来看:
核心问题:你的Authenticator为什么完全没被调用?
你设置的Authenticator完全没触发,根本原因是你的代理用的是NTLM认证——这是Java 11 HttpClient的一个大坑:
- Java 11 HttpClient的默认认证机制只支持标准的Basic/Digest这类HTTP认证协议,对微软的私有NTLM协议完全不兼容。代理返回的NTLM挑战请求,HttpClient根本识别不了,自然不会去调用你写的
getPasswordAuthentication()方法,直接返回407。 - 你试过的两个方案为什么没用?
- 手动加
Proxy-Authorization头:NTLM不是一次性的Basic认证,它需要多轮握手(请求→代理返回NTLM挑战→客户端加密响应→再发送),静态添加的头根本完不成这个流程,所以无效。 - 设置
jdk.http.auth.tunneling.disabledSchemes="":这个参数只针对HTTPS代理的CONNECT隧道请求,而且前提是代理用的是标准认证,对NTLM代理完全不起作用。
- 手动加
针对NTLM代理的可行解决思路
既然核心是NTLM的兼容性问题,目前有几个实际可行的方案:
方案1:退回到HttpURLConnection(最省事)
你原来的HttpURLConnection其实是支持NTLM代理认证的(依赖JDK底层的java.net.Authenticator),如果你的核心需求只是发PATCH请求,完全可以用反射绕过HttpURLConnection的方法限制,代码示例:
import java.io.BufferedReader; import java.io.OutputStream; import java.lang.reflect.Field; import java.net.Authenticator; import java.net.HttpURLConnection; import java.net.PasswordAuthentication; import java.net.URL; import java.nio.charset.StandardCharsets; public static void main(String[] args) throws Exception { String url = "http://www.google.com/"; String proxyHost = "10.10.10.1"; int proxyPort = 8080; String authUser = "user"; String authPassword = "password"; // 手动设置代理和全局认证 System.setProperty("http.proxyHost", proxyHost); System.setProperty("http.proxyPort", String.valueOf(proxyPort)); Authenticator.setDefault(new Authenticator() { @Override protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication(authUser, authPassword.toCharArray()); } }); // 发送PATCH请求 HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); // 反射修改method字段,绕过官方对PATCH方法的限制 Field methodField = HttpURLConnection.class.getDeclaredField("method"); methodField.setAccessible(true); methodField.set(conn, "PATCH"); // 若需要发送请求体,可设置以下内容 conn.setDoOutput(true); conn.setRequestProperty("Content-Type", "application/json"); try (OutputStream os = conn.getOutputStream()) { byte[] input = "{\"key\":\"value\"}".getBytes(StandardCharsets.UTF_8); os.write(input, 0, input.length); } // 处理响应 System.out.println("响应状态码:" + conn.getResponseCode()); try (BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) { StringBuilder response = new StringBuilder(); String responseLine; while ((responseLine = br.readLine()) != null) { response.append(responseLine.trim()); } System.out.println("响应内容:" + response); } }
方案2:用Apache HttpClient 4.x版本(最稳定)
注意不要用5.x版本,因为HttpClient 5.x已经正式废弃了NTLM支持,而4.x版本对NTLM的握手流程处理得非常成熟:
import org.apache.http.HttpHost; import org.apache.http.auth.NTCredentials; import org.apache.http.client.CredentialsProvider; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPatch; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.BasicCredentialsProvider; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.util.EntityUtils; public static void main(String[] args) throws Exception { String url = "http://www.google.com/"; String proxyHost = "10.10.10.1"; int proxyPort = 8080; String authUser = "user"; String authPassword = "password"; // 配置NTLM代理认证信息 CredentialsProvider credsProvider = new BasicCredentialsProvider(); credsProvider.setCredentials( new org.apache.http.auth.AuthScope(proxyHost, proxyPort), new NTCredentials(authUser, authPassword, "", "") ); // 创建带代理的客户端 CloseableHttpClient client = HttpClientBuilder.create() .setProxy(new HttpHost(proxyHost, proxyPort)) .setDefaultCredentialsProvider(credsProvider) .build(); // 发送GET请求测试连通性 HttpGet getRequest = new HttpGet(url); try (CloseableHttpResponse response = client.execute(getRequest)) { System.out.println("GET响应状态码:" + response.getStatusLine().getStatusCode()); System.out.println("GET响应内容:" + EntityUtils.toString(response.getEntity())); } // 发送PATCH请求 HttpPatch patchRequest = new HttpPatch(url); patchRequest.setEntity(new StringEntity("{\"key\":\"value\"}")); patchRequest.setHeader("Content-Type", "application/json"); try (CloseableHttpResponse response = client.execute(patchRequest)) { System.out.println("PATCH响应状态码:" + response.getStatusLine().getStatusCode()); } }
方案3:自定义NTLM握手逻辑(不推荐)
如果必须用Java 11 HttpClient,那只能手动实现NTLM的多轮握手流程:
- 先发送一个不带认证的请求,获取代理返回的407响应中的
Proxy-Authenticate: NTLM <挑战字符串> - 用NTLM算法加密用户名密码,生成响应字符串
- 再发送带
Proxy-Authorization: NTLM <响应字符串>的请求
这个过程需要处理NTLM的版本(v1/v2)、加密细节,复杂度极高,除非有特殊需求,否则完全没必要。
如何确认代理用的是NTLM认证?
你可以用curl命令快速验证:
curl -x 10.10.10.1:8080 -I http://www.google.com
如果返回的响应头里包含Proxy-Authenticate: NTLM,那就能100%确认是NTLM代理了。
总的来说,NTLM作为微软已经在逐步淘汰的协议,新的Java HTTP客户端基本都放弃了支持,所以用旧的成熟方案(HttpURLConnection/Apache HttpClient 4.x)是最省心的选择。




