如何在JDK HttpClient中实现curl --ntlm参数对应的NTLM身份认证
问题描述
我对各类HTTP上层协议并不算十分精通,目前我可以通过以下curl命令访问公司的某一个API:
curl --http1.1 --ntlm -u 'user:pass' 'MY_URL'
该命令可以正常运行,请注意命令中指定了--ntlm参数。
我现在尝试用http-jdk-client复现相同的请求逻辑,代码如下:
var client = HttpClient.newBuilder() .authenticator(new Authenticator() { @Override protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication(<user>, <pass>.toCharArray()); } }) .sslContext(sslContext()) // an SSL Context that trusts everyone .version(HttpClient.Version.HTTP_1_1) .build(); var request = HttpRequest.newBuilder() .uri(URI.create(url)) .build(); client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) .thenApply(HttpResponse::body) .thenAccept(System.out::println) .join();
这段代码运行后返回401错误。
如果我使用相同的curl命令但去掉--ntlm参数,也会得到同样的401错误。因此我判断需要在JDK客户端中复现--ntlm参数的效果,请问有什么相关的思路或者解决方案吗?谢谢。
解决方案
原因说明
JDK自带的HttpClient默认未开启NTLM认证支持,仅配置基础的Authenticator无法触发NTLM的三次握手认证流程,因此会返回401未授权错误。
方案1:开启JDK HttpClient的NTLM支持
通过配置系统属性启用NTLM认证,可直接适配现有代码逻辑:
- 代码初始化阶段添加全局配置,开启NTLM认证支持:
// 测试环境可直接开启所有域名的NTLM透明认证 System.setProperty("jdk.http.ntlm.transparentAuth", "allHosts"); // 禁用默认的NTLM认证拦截规则 System.setProperty("jdk.http.auth.tunneling.disabledSchemes", ""); System.setProperty("jdk.http.auth.proxying.disabledSchemes", "");
- 调整账号格式:如果是域账号,
PasswordAuthentication的用户名字段需要填写域名\\用户名格式,比如"COMPANY\\zhangsan",无域名则直接填用户名即可。
调整后的完整代码如下:
// 全局配置放到代码初始化位置 System.setProperty("jdk.http.ntlm.transparentAuth", "allHosts"); System.setProperty("jdk.http.auth.tunneling.disabledSchemes", ""); System.setProperty("jdk.http.auth.proxying.disabledSchemes", ""); var client = HttpClient.newBuilder() .authenticator(new Authenticator() { @Override protected PasswordAuthentication getPasswordAuthentication() { // 域账号格式:域名\\用户名,无域名直接填用户名 return new PasswordAuthentication("your_username", "your_password".toCharArray()); } }) .sslContext(sslContext()) .version(HttpClient.Version.HTTP_1_1) .followRedirects(HttpClient.Redirect.NORMAL) .build(); var request = HttpRequest.newBuilder() .uri(URI.create(url)) .build(); client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) .thenApply(HttpResponse::body) .thenAccept(System.out::println) .join();
方案2:使用Apache HttpClient 5(兼容性更好)
如果JDK自带的HttpClient方案不稳定,推荐使用原生支持NTLM认证的Apache HttpClient 5,企业场景下兼容性更高:
- 引入Maven依赖:
<dependency> <groupId>org.apache.httpcomponents.client5</groupId> <artifactId>httpclient5</artifactId> <version>5.2.1</version> </dependency>
- 实现代码示例:
import org.apache.hc.client5.http.classic.methods.HttpGet; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.HttpClients; import org.apache.hc.client5.http.auth.AuthScope; import org.apache.hc.client5.http.auth.CredentialsProvider; import org.apache.hc.client5.http.auth.NTCredentials; import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider; import org.apache.hc.core5.http.io.entity.EntityUtils; // 配置NTLM凭证 // 参数依次为:用户名、密码、本地主机名(可填null)、域名(无域则填空字符串) NTCredentials credentials = new NTCredentials("your_username", "your_password".toCharArray(), null, "your_domain"); CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); // 替换为你API的实际域名和端口,HTTPS默认端口443,HTTP默认端口80 credentialsProvider.setCredentials(new AuthScope("your_api_host", 443), credentials); // 构建HTTP客户端 try (CloseableHttpClient httpClient = HttpClients.custom() .setDefaultCredentialsProvider(credentialsProvider) .setSSLContext(sslContext()) .build()) { HttpGet request = new HttpGet("YOUR_API_URL"); httpClient.execute(request, response -> { System.out.println("响应状态码:" + response.getCode()); System.out.println("响应内容:" + EntityUtils.toString(response.getEntity())); return null; }); }
注意事项
- NTLM是连接绑定的认证协议,需要保证客户端开启连接复用,不要每次请求都新建客户端实例
- 如果是生产环境使用方案1,建议将
jdk.http.ntlm.transparentAuth设为trustedHosts,同时配置jdk.http.ntlm.trustedHosts为你公司API的域名列表,避免安全风险
内容的提问来源于stack exchange,提问作者Eugene
相关产品推荐
相关产品推荐

