
在现代企业级应用中,与依赖Windows NTLM认证的后端服务进行交互是常见需求。然而,Spring Framework的响应式Web客户端——WebClient,不像其前身RestTemplate那样直接支持NTLM认证,这给开发者带来了一定的挑战。本文将详细介绍如何通过自定义ExchangeFilterFunction并结合JCIFS库,为WebClient实现健壮的Windows NTLM认证机制。
NTLM(NT LAN Manager)是一种挑战-响应(Challenge-Response)协议,用于验证用户身份。其基本流程涉及客户端发送认证请求(Type 1消息),服务器返回挑战(Type 2消息),客户端根据挑战和用户凭据计算响应(Type 3消息)并发送给服务器,最终服务器验证响应。
RestTemplate可以通过配置HttpClient(如Apache HttpClient)并使用NTCredentials来相对容易地实现NTLM认证。然而,WebClient通常默认使用Reactor Netty作为底层HTTP客户端,且其ExchangeFilterFunctions主要针对Basic认证等更简单的机制。直接使用basicAuthentication或手动设置Authorization头并不能满足NTLM的挑战-响应流程。因此,我们需要一个能够拦截请求和响应,并根据NTLM协议进行多步处理的自定义过滤器。
为了在WebClient中实现NTLM认证,我们可以利用JCIFS库,它提供了NTLM协议的Java实现。核心思想是创建一个ExchangeFilterFunction,它能够:
以下是实现NtlmAuthorizedClientExchangeFilterFunction的详细代码:
import jcifs.ntlmssp.NtlmFlags;
import jcifs.ntlmssp.Type1Message;
import jcifs.ntlmssp.Type2Message;
import jcifs.ntlmssp.Type3Message;
import jcifs.util.Base64;
import org.springframework.http.HttpHeaders;
import org.springframework.web.reactive.function.client.ClientRequest;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
import org.springframework.web.reactive.function.client.ExchangeFunction;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
import java.io.IOException;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
/**
* Spring WebClient的NTLM认证过滤器。
* 使用JCIFS库实现NTLM挑战-响应机制。
*/
public final class NtlmAuthorizedClientExchangeFilterFunction implements ExchangeFilterFunction {
private final String domain;
private final String username;
private final String password;
private final boolean doSigning;
private final int lmCompatibility;
/**
* 构造函数。
* @param domain NTLM域
* @param username 用户名
* @param password 密码
* @param doSigning 是否进行消息签名(推荐为true以增强安全性)
* @param lmCompatibility LM兼容性级别 (0-5),影响密码哈希算法
*/
public NtlmAuthorizedClientExchangeFilterFunction(String domain, String username, String password, boolean doSigning, int lmCompatibility) {
this.domain = domain;
this.username = username;
this.password = password;
this.doSigning = doSigning;
this.lmCompatibility = lmCompatibility;
// 设置JCIFS的LM兼容性系统属性
System.setProperty("jcifs.smb.lmCompatibility", Integer.toString(lmCompatibility));
}
@Override
public Mono<ClientResponse> filter(final ClientRequest request, final ExchangeFunction next) {
// NTLM认证需要状态,因此在每次请求中创建一个新的上下文
// NTLM上下文的标志,包括请求签名和NTLMSSP_NEGOTIATE_ALWAYS_SIGN
int flags = NtlmFlags.NTLMSSP_NEGOTIATE_UNICODE |
NtlmFlags.NTLMSSP_NEGOTIATE_OEM |
NtlmFlags.NTLMSSP_REQUEST_TARGET |
NtlmFlags.NTLMSSP_NEGOTIATE_NTLM;
if (doSigning) {
flags |= NtlmFlags.NTLMSSP_NEGOTIATE_SIGN | NtlmFlags.NTLMSSP_NEGOTIATE_ALWAYS_SIGN;
}
try {
// 第一步:发送Type 1消息
Type1Message type1 = new Type1Message(flags, domain, null); // workstation留空,JCIFS会自动处理
byte[] type1Bytes = type1.toByteArray();
return next.exchange(addNtlmHeader(request, type1Bytes))
// 确保请求按顺序处理,以维持HTTP连接和状态
.publishOn(Schedulers.single())
.flatMap(clientResponse -> {
// 检查响应是否包含NTLM挑战
List<String> ntlmAuthHeaders = getNtlmAuthHeaders(clientResponse);
if (ntlmAuthHeaders.isEmpty()) {
// 如果没有NTLM挑战,则可能是认证成功或非NTLM认证,直接返回响应
// 或者根据业务需求抛出错误
return Mono.just(clientResponse);
}
// 提取Type 2消息
String ntlmHeader = ntlmAuthHeaders.get(0);
if (ntlmHeader.length() <= 5) { // "NTLM " + base64 content
return Mono.error(new IOException("Invalid NTLM challenge header: " + ntlmHeader));
}
try {
byte[] type2Bytes = Base64.decode(ntlmHeader.substring(5));
Type2Message type2 = new Type2Message(type2Bytes);
// 第二步:根据Type 2消息和凭据生成Type 3消息
Type3Message type3 = new Type3Message(type2, password, domain, username);
byte[] type3Bytes = type3.toByteArray();
// 重新发送带有Type 3消息的请求
return next.exchange(addNtlmHeader(request, type3Bytes));
} catch (IOException e) {
return Mono.error(new RuntimeException("Failed to process NTLM Type 2 message or generate Type 3 message", e));
}
});
} catch (IOException e) {
return Mono.error(new RuntimeException("Failed to generate NTLM Type 1 message", e));
}
}
/**
* 从ClientResponse中提取NTLM认证头。
* @param clientResponse 客户端响应
* @return 包含"NTLM"前缀的WWW-Authenticate头列表
*/
private static List<String> getNtlmAuthHeaders(ClientResponse clientResponse) {
List<String> wwwAuthHeaders = clientResponse.headers().header(HttpHeaders.WWW_AUTHENTICATE);
// 过滤出NTLM头,并按长度排序(通常更长的包含Type 2消息)
return wwwAuthHeaders.stream()
.filter(h -> h.startsWith("NTLM"))
.sorted(Comparator.comparingInt(String::length).reversed()) // 优先处理更长的NTLM头
.collect(Collectors.toList());
}
/**
* 向请求中添加NTLM认证头。
* @param clientRequest 原始请求
* @param ntlmPayload NTLM消息的字节数组
* @return 添加了认证头的新请求
*/
private ClientRequest addNtlmHeader(ClientRequest clientRequest, byte[] ntlmPayload) {
return ClientRequest.from(clientRequest)
.header(HttpHeaders.AUTHORIZATION, "NTLM ".concat(Base64.encode(ntlmPayload)))
.build();
}
}要使用上述自定义NTLM过滤器,您需要将其添加到WebClient.builder()的过滤器链中:
import org.springframework.web.reactive.function.client.WebClient;
public class NtlmWebClientConfig {
public WebClient ntlmAuthenticatedWebClient() {
String domain = "YOUR_DOMAIN"; // 例如 "MYDOMAIN"
String username = "YOUR_USERNAME";
String password = "YOUR_PASSWORD";
boolean doSigning = true; // 推荐开启消息签名
int lmCompatibility = 3; // 根据NTLM服务器配置调整,常见值如3
NtlmAuthorizedClientExchangeFilterFunction ntlmFilter =
new NtlmAuthorizedClientExchangeFilterFunction(domain, username, password, doSigning, lmCompatibility);
return WebClient.builder()
.filter(ntlmFilter)
// 可以添加其他过滤器或配置
.baseUrl("https://my.ntlm.protected.service")
.build();
}
public static void main(String[] args) {
NtlmWebClientConfig config = new NtlmWebClientConfig();
WebClient webClient = config.ntlmAuthenticatedWebClient();
webClient.get()
.uri("/some/resource")
.retrieve()
.bodyToMono(String.class)
.doOnNext(System.out::println)
.doOnError(e -> System.err.println("Error: " + e.getMessage()))
.block(); // 阻塞以等待结果,实际应用中通常使用订阅
}
}为了使上述代码正常工作,您需要在项目的pom.xml(Maven)或build.gradle(Gradle)中添加JCIFS库的依赖:
Maven:
<dependency>
<groupId>jcifs</groupId>
<artifactId>jcifs</artifactId>
<version>1.3.17</version> <!-- 请检查最新稳定版本 -->
</dependency>Gradle:
implementation 'jcifs:jcifs:1.3.17' // 请检查最新稳定版本
注意: JCIFS库的最新版本可能在Maven中央仓库中有所变动,请查阅官方文档或Maven Central以获取最新稳定版本。
通过实现自定义的ExchangeFilterFunction并结合JCIFS库,我们成功地为Spring WebClient带来了Windows NTLM认证的能力。这种方法遵循了NTLM的挑战-响应协议,并允许开发者在响应式应用中与NTLM保护的资源进行交互。虽然实现过程比简单的Basic认证复杂,但其提供了高度的灵活性和控制力。在实际应用中,务必注意凭据的安全管理、lmCompatibility的正确配置以及健壮的错误处理。对于利用当前用户上下文进行认证的特殊需求,则需要考虑更深层次的系统集成方案。
以上就是Spring WebClient实现Windows NTLM认证的专业指南的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号