You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

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的多轮握手流程:

  1. 先发送一个不带认证的请求,获取代理返回的407响应中的Proxy-Authenticate: NTLM <挑战字符串>
  2. 用NTLM算法加密用户名密码,生成响应字符串
  3. 再发送带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)是最省心的选择。

火山引擎 最新活动