PAYATHON 2026

Java HttpClient 代理认证返回 407,Authenticator 为何未调用?

数据小陈

结论

如果代理要求 NTLM,而当前 JDK HttpClient 无法处理代理返回的 NTLM 挑战,Authenticator 可能不会被调用,请求最终会以 407 结束。Authenticator 可以提供用户名和密码,但无法让客户端支持原本不支持的认证协议。先检查代理响应中的 Proxy-Authenticate,确认代理要求的认证方案,再核对该方案是否受当前 JDK 客户端支持。

检查代理返回的认证挑战

发送请求后,读取状态码和 Proxy-Authenticate 响应头:

HttpResponse<String> response = client.send(
    request,
    HttpResponse.BodyHandlers.ofString()
);

System.out.println("status=" + response.statusCode());
System.out.println("Proxy-Authenticate=" +
    response.headers().allValues("Proxy-Authenticate"));

如果响应头显示 NTLM,代理要求的就是 NTLM。即使配置了 Authenticator,也不能据此判断客户端支持这种认证方式。支持情况可能因 JDK 版本和实现而异,应以实际运行环境和代理返回的挑战为准。

还可以在回调中检查请求方类型,确认 JDK 是否向认证器请求代理凭据:

Authenticator authenticator = new Authenticator() {
    @Override
    protected PasswordAuthentication getPasswordAuthentication() {
        System.out.println("requestorType=" + getRequestorType());
        System.out.println("protocol=" + getRequestingProtocol());
        System.out.println("host=" + getRequestingHost());
        return new PasswordAuthentication(
            authUser,
            authPassword.toCharArray()
        );
    }
};

不要在日志中输出密码。如果回调没有触发,可结合 Proxy-Authenticate 检查代理要求的认证方案。若当前客户端不支持该方案,仅设置用户名和密码无法解决问题。

几个配置的适用范围

  • java.net.useSystemProxies 用于选择系统代理。代码显式调用 .proxy(proxySelector) 后,HttpClient 使用的是传入的 ProxySelector,不能据此判断它会沿用旧代码中的系统代理或认证行为。
  • jdk.http.auth.tunneling.disabledSchemes 用于配置通过 CONNECT 建立隧道时禁用的认证方案。调整这个属性不会让客户端新增 NTLM 支持。对于普通 HTTP 代理请求,也不能仅凭这个属性判断认证是否生效。
  • 手动设置 Proxy-Authorization,只有在客户端和代理使用相同且受支持的认证方案时才可能有效。NTLM 等挑战响应认证需要完整协商,不能用一个固定头部简单替代。

可采用的处理方式

确认代理的认证方案以及当前 JDK HttpClient 的支持情况后,可以让代理启用客户端支持的认证方案,改用明确支持该代理认证方式的客户端,或继续使用已验证可用的旧实现。选择其他客户端时,应核对其所用版本对代理 NTLM 的支持情况,不能只根据客户端名称判断。

备注:内容仅供参考。