You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring WebClient使用HttpComponentsClientHttpConnector的NTLM认证问题

问题根因

该问题是Spring WebClient整合Apache HttpClient5时的已知限制:

  • NTLM为有状态认证协议,完整握手流程需要先后发起3次请求,要求请求体支持重复读取
  • 默认HttpComponentsClientHttpRequest初始化的ReactiveEntityProducer为单次消费实现,第一次读取后就会标记为已消费,后续握手的第二次、第三次请求无法重新读取请求体,直接抛出异常导致认证失败

解决方案

方案1:配置HttpClient5适配NTLM握手逻辑(推荐,改造成本最低)

利用HttpClient5自带的expect: 100-continue逻辑配合自定义重试策略,让客户端在NTLM握手完成前不发送完整请求体,从根源避免重复读取问题,仅需要在原有代码基础上补充重试策略配置即可:

BasicCredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(
    new AuthScope("https", "some-host", 443, null, StandardAuthScheme.NTLM),
    new NTCredentials("test-user", "test-password".toCharArray(), null, null)
);

// 自定义重试策略,适配NTLM 3次握手的重试需求
DefaultHttpRequestRetryStrategy retryStrategy = DefaultHttpRequestRetryStrategyBuilder.create()
        .setMaxRetries(3)
        .retryOnAllNonIdempotentMethods() // 允许POST等非幂等请求在认证场景下重试
        .build();

WebClient webClient = WebClient.builder()
    .clientConnector(new HttpComponentsClientHttpConnector
        (HttpAsyncClients
            .custom()
            .setDefaultCredentialsProvider(credsProvider)
            .setTargetAuthenticationStrategy(DefaultAuthenticationStrategy.INSTANCE)
            .setRetryStrategy(retryStrategy) // 注入自定义重试策略
            .setDefaultRequestConfig(
                RequestConfig.custom()
                    .setAuthenticationEnabled(true)
                    .setTargetPreferredAuthSchemes(Collections.singletonList(StandardAuthScheme.NTLM))
                    .setExpectContinueEnabled(true) // 已配置的100-continue避免握手阶段提前发送请求体
                    .build())
            .build()))
    .build();

// 后续请求逻辑无需修改
webClient
    .method(HttpMethod.POST)
    .uri("https://some-host/execute")
    .header("Content-Type", "text/xml; charset=utf-8")
    .header("SOAPAction", "http://schemas.microsoft.com/exchange/services/2006/messagesCreateItem")
    .header("Accept-Encoding", "gzip, deflate, br")
    .header("Accept-Language", "ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7")
    .header("Expect", "100-continue")
    .body(BodyInserters.fromValue(
        "<SOME_XML_BODY>"
    ))
    .exchangeToMono(clientResponse -> {
        return clientResponse.bodyToMono(Object.class);
    })
    .subscribe();

方案2:自定义连接器支持可重读请求体

如果方案1不符合你的业务场景,可扩展HttpComponentsClientHttpConnector,重写请求创建逻辑,将默认的单次消费ReactiveEntityProducer替换为支持重复读取的实现,该方案更适合请求体本身是内存缓存对象的场景。

内容的提问来源于stack exchange,提问作者maximusKon

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.26 19:54:02