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
相关产品推荐
相关产品推荐

