展开菜单
首页 精品内容 本月促销 装机必备 Windows PDF教程 macOS软件 IOS软件 Android AI 专题
全部分类

当前位置:

首页 > 编程开发 > Spring WebClient实现NTLM认证教程

Spring WebClient实现NTLM认证教程

本文详细阐述了如何在SpringWebClient中实现WindowsNTLM认证。鉴于WebClient原生不支持NTLM,核心解决方案是开发一个自定义的ExchangeFilterFunction,结合JCIFS库来处理NTLM协议的挑战-响应握手过程。教程提供了完整的代码示例,并解释了如何构建NTLM认证上下文、发送认证头,以及将此过滤器集成到WebClient中,同时讨论了相关的配置和注意事项。

本文目录

    Spring WebClient实现Windows NTLM认证的专业指南

    本文详细阐述了如何在Spring WebClient中实现Windows NTLM认证。鉴于WebClient原生不支持NTLM,核心解决方案是开发一个自定义的`ExchangeFilterFunction`,结合JCIFS库来处理NTLM协议的挑战-响应握手过程。教程提供了完整的代码示例,并解释了如何构建NTLM认证上下文、发送认证头,以及将此过滤器集成到WebClient中,同时讨论了相关的配置和注意事项。

    引言

    Spring WebClient作为Spring Framework 5中引入的非阻塞、响应式HTTP客户端,在现代微服务架构中广受欢迎。然而,与传统的RestTemplate相比,WebClient在处理某些特定认证机制,如Windows NTLM认证时,并未提供开箱即用的支持。NTLM认证是一种基于挑战/响应机制的协议,常用于Windows域环境。本文将深入探讨如何通过自定义ExchangeFilterFunction并结合JCIFS库,为Spring WebClient添加NTLM认证能力。

    NTLM认证机制概述

    NTLM(NT LAN Manager)认证是一个多步骤的挑战-响应协议,通常涉及以下过程:

    1. 客户端请求:客户端向服务器发起请求,不带认证信息。
    2. 服务器挑战(Type 2 Message):服务器返回HTTP 401 Unauthorized状态码,并在WWW-Authenticate响应头中包含一个NTLM挑战(Type 2消息)。
    3. 客户端响应(Type 3 Message):客户端使用其凭据(用户名、密码、域)和服务器的挑战信息生成一个NTLM响应(Type 3消息),并将其放入Authorization请求头中,再次发送请求。
    4. 服务器验证:服务器验证客户端的响应,如果成功,则返回资源。

    由于WebClient的响应式特性,这个多步握手过程需要通过一个能够捕获并处理中间响应的机制来实现,ExchangeFilterFunction正是为此而生。

    使用JCIFS实现WebClient NTLM认证

    为了在WebClient中实现NTLM认证,我们需要一个能够处理NTLM协议细节的库。JCIFS是一个流行的Java库,提供了NTLM协议的实现。我们将创建一个自定义的ExchangeFilterFunction,利用JCIFS来管理NTLM握手的状态和生成认证消息。

    核心组件:NtlmAuthorizedClientExchangeFilterFunction

    这个自定义过滤器将拦截HTTP请求,并在需要时注入NTLM认证头。它需要维护NTLM握手的状态,这通过jcifs.ntlmssp.NtlmContext实现。

    首先,确保你的项目中包含了JCIFS依赖:

    
        org.samba.jcifs
        jcifs
        2.1.30 
    

    以下是NtlmAuthorizedClientExchangeFilterFunction的实现代码:

    import jcifs.ntlmssp.NtlmPasswordAuthentication;
    import jcifs.ntlmssp.NtlmContext;
    import jcifs.smb.SmbException;
    import jcifs.util.Base64;
    import org.jetbrains.annotations.NotNull;
    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;
    
    public final class NtlmAuthorizedClientExchangeFilterFunction implements ExchangeFilterFunction {
    
        private final NtlmPasswordAuthentication ntlmPasswordAuthentication;
        private final boolean doSigning;
    
        /**
         * 构造函数,初始化NTLM认证凭据和相关配置。
         * @param domain 域
         * @param username 用户名
         * @param password 密码
         * @param doSigning 是否进行消息签名
         * @param lmCompatibility LM兼容性级别 (0-5),影响NTLMv1/NTLMv2协商
         */
        public NtlmAuthorizedClientExchangeFilterFunction(String domain, String username, String password, boolean doSigning, int lmCompatibility) {
            this.ntlmPasswordAuthentication = new NtlmPasswordAuthentication(domain, username, password);
            this.doSigning = doSigning;
            // 设置JCIFS的LM兼容性级别,影响NTLMv1/NTLMv2协商
            System.setProperty("jcifs.smb.lmCompatibility", Integer.toString(lmCompatibility));
        }
    
        @Override
        public Mono filter(final ClientRequest request, final ExchangeFunction next) {
            // 为每个请求创建一个新的NTLM上下文,以处理多步握手
            NtlmContext ntlmContext = new NtlmContext(ntlmPasswordAuthentication, doSigning);
            try {
                // 第一次请求:发送Type 1消息
                // initSecContext(new byte[0], 0, 0) 生成Type 1消息的payload
                return next.exchange(addNtlmHeader(request, ntlmContext.initSecContext(new byte[0], 0, 0)))
                    // 确保请求是顺序处理的,这对于HTTP Keep-Alive和NTLM多步握手至关重要
                    .publishOn(Schedulers.single())
                    .flatMap(clientResponse -> {
                        // 检查响应头中是否包含NTLM挑战
                        List ntlmAuthHeaders = getNtlmAuthHeaders(clientResponse);
                        if (ntlmAuthHeaders.isEmpty()) {
                            // 如果没有NTLM挑战,则直接返回原始响应或抛出错误
                            // 这里简化处理,实际应用中可能需要更细致的错误判断
                            return Mono.error(new IllegalStateException("NTLM authentication expected but no NTLM challenge received."));
                        }
                        String ntlmHeader = ntlmAuthHeaders.get(0);
                        if (ntlmHeader.length() <= 5) { // "NTLM " 占5个字符
                            return Mono.error(new IllegalStateException("Invalid NTLM challenge header."));
                        }
                        try {
                            // 解析Type 2消息,并生成Type 3消息
                            byte[] type2 = Base64.decode(ntlmHeader.substring(5));
                            // initSecContext(type2, 0, type2.length) 生成Type 3消息的payload
                            return next.exchange(addNtlmHeader(request, ntlmContext.initSecContext(type2, 0, type2.length)));
                        } catch (IOException e) {
                            return Mono.error(new IllegalStateException("Failed to decode NTLM Type 2 message or generate Type 3 message.", e));
                        }
                    });
            } catch (SmbException e) {
                return Mono.error(new IllegalStateException("Failed to initialize NTLM security context.", e));
            }
        }
    
        /**
         * 从ClientResponse中提取NTLM认证头。
         * @param clientResponse 客户端响应
         * @return 包含NTLM挑战的头列表
         */
        @NotNull
        private static List getNtlmAuthHeaders(ClientResponse clientResponse) {
            List wwwAuthHeaders = clientResponse.headers().header(HttpHeaders.WWW_AUTHENTICATE);
            // 过滤出以"NTLM"开头的头,并按长度排序(通常更长的包含更多信息)
            return wwwAuthHeaders.stream()
                    .filter(h -> h.startsWith("NTLM"))
                    .sorted(Comparator.comparingInt(String::length))
                    .collect(Collectors.toList());
        }
    
        /**
         * 向ClientRequest添加NTLM认证头。
         * @param clientRequest 客户端请求
         * @param ntlmPayload NTLM消息的字节数组
         * @return 带有NTLM认证头的新ClientRequest
         */
        private ClientRequest addNtlmHeader(ClientRequest clientRequest, byte[] ntlmPayload) {
            return ClientRequest
                .from(clientRequest)
                .header(HttpHeaders.AUTHORIZATION, "NTLM ".concat(Base64.encode(ntlmPayload)))
                .build();
        }
    }

    代码详解

    1. 构造函数:

      • 接收domain、username、password用于构建NtlmPasswordAuthentication对象,这是JCIFS用于存储用户凭据的核心类。
      • doSigning参数决定是否启用消息签名。
      • lmCompatibility参数通过System.setProperty("jcifs.smb.lmCompatibility", ...)设置,它影响NTLMv1和NTLMv2的协商行为。通常建议设置为3或更高,以提高安全性。
    2. filter方法:

      • NTLM上下文:NtlmContext ntlmContext = new NtlmContext(ntlmPasswordAuthentication, doSigning); 为每个请求创建一个新的NTLM上下文。这是关键,因为NTLM握手是状态化的。
      • 第一次请求 (Type 1):ntlmContext.initSecContext(new byte[0], 0, 0) 生成NTLM Type 1消息的负载。这个负载被Base64编码后,添加到Authorization头中,格式为NTLM 。
      • publishOn(Schedulers.single()):这行代码至关重要。它确保了对next.exchange的后续操作在同一个调度器上执行,从而保证了HTTP Keep-Alive的有效性,使得NTLM的两次请求可以在同一个TCP连接上完成。这对于NTLM认证的成功至关重要。
      • 处理服务器响应:
        • flatMap操作符用于处理第一个请求的响应。
        • getNtlmAuthHeaders(clientResponse) 尝试从响应的WWW-Authenticate头中提取NTLM挑战(Type 2消息)。
        • 如果找到Type 2消息,它会被Base64解码,然后传递给ntlmContext.initSecContext(type2, 0, type2.length),以生成NTLM Type 3消息的负载。
        • Type 3消息负载同样被Base64编码,并作为Authorization头发送第二次请求。
      • 错误处理:捕获可能发生的SmbException或IOException,并将其包装为Mono.error返回。
    3. 辅助方法:

      • getNtlmAuthHeaders:从响应头中解析出以"NTLM"开头的WWW-AUTHENTICATE头。
      • addNtlmHeader:创建一个新的ClientRequest,并添加带有Base64编码NTLM负载的Authorization头。

    集成到WebClient

    将这个自定义过滤器集成到WebClient中非常简单:

    import org.springframework.web.reactive.function.client.WebClient;
    
    public class WebClientNtlmExample {
    
        public static void main(String[] args) {
            // NTLM认证凭据
            String domain = "YOUR_DOMAIN";
            String username = "YOUR_USERNAME";
            String password = "YOUR_PASSWORD";
            boolean doSigning = false; // 根据需要设置
            int lmCompatibility = 3;   // 推荐使用3或更高
    
            // 创建NtlmAuthorizedClientExchangeFilterFunction实例
            NtlmAuthorizedClientExchangeFilterFunction ntlmFilter =
                    new NtlmAuthorizedClientExchangeFilterFunction(domain, username, password, doSigning, lmCompatibility);
    
            // 构建WebClient,并添加NTLM过滤器
            WebClient webClient = WebClient.builder()
                    .filter(ntlmFilter) // 添加自定义的NTLM认证过滤器
                    .baseUrl("https://my.url.com") // 你的目标URL
                    .build();
    
            // 发起请求
            webClient.get()
                    .uri("/") // 具体的路径
                    .retrieve()
                    .bodyToMono(String.class)
                    .doOnSuccess(response -> System.out.println("成功获取响应: " + response))
                    .doOnError(error -> System.err.println("请求失败: " + error.getMessage()))
                    .block(); // 阻塞等待结果,在实际应用中通常避免在主线程中使用block()
        }
    }

    注意事项

    1. JCIFS版本:确保使用兼容的JCIFS版本。
    2. lmCompatibility:这个系统属性影响NTLM协商的安全级别。
      • 0:仅NTLMv1
      • 3:NTLMv1和NTLMv2,优先NTLMv2
      • 5:仅NTLMv2 根据你的NTLM服务器配置选择合适的值。
    3. 错误处理:示例代码中的错误处理较为简化。在生产环境中,应实现更健壮的错误捕获和日志记录机制。例如,当NTLM认证失败时,可以抛出特定的异常。
    4. publishOn(Schedulers.single()):此设置是为了确保NTLM握手的两次HTTP请求在同一个TCP连接上进行,这对于依赖Keep-Alive的NTLM认证至关重要。如果移除或错误配置,可能导致认证失败。
    5. 凭据安全:在实际应用中,不应将凭据硬编码在代码中,而应通过安全配置(如Spring Cloud Config、Vault等)或环境变量进行管理。
    6. 域和用户名格式:NtlmPasswordAuthentication通常接受DOMAIN\\username或username(如果域已在构造函数中指定)的格式。

    关于当前用户上下文认证

    对于在Windows环境下运行,并希望使用当前登录用户凭据进行NTLM认证的需求,上述自定义ExchangeFilterFunction方法不直接支持。这是因为NtlmPasswordAuthentication需要显式提供用户名和密码。

    使用当前用户上下文进行NTLM认证通常需要更深层次的操作系统集成,例如:

    • Java GSS-API/Kerberos:Java的GSS-API可以与Windows的Kerberos票据集成,但配置复杂。
    • 第三方库:例如,Waffle (Windows Auth Filter for Java) 库可以实现基于Windows当前用户上下文的认证,但它通常作为Servlet过滤器或Spring Security集成,而非直接用于WebClient。
    • JVM启动参数:在某些特定JVM配置下,可以通过JVM参数尝试启用本地NTLM认证,但这通常依赖于JRE的内部实现,且跨平台性差。

    因此,如果必须使用当前用户上下文认证,可能需要考虑在WebClient层面之外的其他解决方案,或者结合特定的平台依赖库。

    总结

    通过实现自定义的ExchangeFilterFunction并结合JCIFS库,Spring WebClient能够有效地支持Windows NTLM认证。这种方法提供了一个灵活且可控的解决方案,能够处理NTLM协议的多步挑战-响应过程。虽然实现相对复杂,但它使得WebClient能够与依赖NTLM认证的企业服务进行无缝交互。在实际应用中,务必关注凭据安全、错误处理以及lmCompatibility等配置细节,以确保认证过程的安全性和稳定性。对于当前用户上下文认证,则需要寻求更深层次的系统集成方案。

    本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
    作者最新文章
    编程开发
    相关文章 更多
    精品专题 更多
    装机必备

    正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

    Windows

    正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

    PDF教程

    正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

    macOS软件

    正软商城macOS软件专区,精选适用于Mac电脑的办公、设计、影音、效率、开发和系统工具,提供软件功能介绍、macOS兼容版本、正版授权及购买下载服务。

    IOS软件

    正软商城iOS软件专区,精选适用于iPhone和iPad的办公、学习、影音、设计、效率及AI应用,提供功能介绍、适用设备、系统要求和正版获取方式等信息。

    AI

    正软商城AI软件专区,汇集AI写作、AI绘画、AI视频、AI办公、AI编程、AI翻译、智能客服和数据分析等人工智能工具,提供功能介绍、适用平台、收费方式及正版购买信息。

    Mac软件 更多
    Aperty macOS版
    Aperty macOS版

    Aperty 是 Skylum 推出的 AI 人像修图软件,支持自然肤质修饰、瑕疵处理、面部与身体调整、预设及批量同步。

    火山引擎 网页版
    火山引擎 网页版

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    立刻MV 网页版
    立刻MV 网页版

    立刻MV 面向音乐人、内容创作者和品牌团队,帮助用户低成本完成歌曲 MV、歌词视频、AI 分镜、图生视频与最终合成。

    蛙蛙写作 网页版
    蛙蛙写作 网页版

    蛙蛙写作 AI 创作工具,覆盖小说、剧本、文案,一键生成文稿,打通文字转视频全流程,高效解决创作难题。

    讯飞智作 网页版
    讯飞智作 网页版

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    超级简历WonderCV 网页版
    超级简历WonderCV 网页版

    超级简历WonderCV是面向应届生和AI小白的智能求职平台,覆盖简历、面试与职业规划。

    音述AI 网页版
    音述AI 网页版

    音述AI,是一个以“用声音述说故事”为核心的 AI 音乐创作与声音分享社区。

    AionClaw macOS版
    AionClaw macOS版

    AionClaw 是可在桌面端运行的任务型 AI 助手,通过数字专家与技能市场组合办公、创作、数据和开发能力。

    Loomy macOS版
    Loomy macOS版

    Loomy 是科大讯飞推出的桌面 AI 助手,可通过对话、任务与工作目录协助处理办公、内容创作和资料整理。

    AirBuddy 3 macOS版
    AirBuddy 3 macOS版

    AirBuddy 3 是面向 macOS 的蓝牙外设增强工具,可集中查看 AirPods、Beats 与苹果设备电量,提供快速连接、播放控制、电量提醒、桌面小组件和多台 Mac 外设接力等功能。

    AirServer macOS版
    AirServer macOS版

    AirServer macOS版可把 Mac 转换为无线投屏接收端,通过 AirPlay 或 Google Cast 接收手机、平板和电脑画面,并支持多设备同时连接、高清镜像及会话录制,适合家庭娱乐、课堂教学、会议演示和应用展示。

    AnyDesk macOS版
    AnyDesk macOS版

    AnyDesk macOS版是一款适用于远程办公、设备维护和客户支持的远程桌面工具,可连接并操作异地计算机,支持文件传输、无人值守访问、会话权限控制及跨平台协作。

    WINDOWS 更多
    讯飞绘文 网页版
    讯飞绘文 网页版

    讯飞绘文网页版是科大讯飞推出的AI图文创作与内容运营工具,覆盖热点选题、文章生成、稿件校对、智能配图、排版发布和数据复盘等环节,适合自媒体创作者及内容团队在线使用。

    Maya(Autodesk Maya) Windows版
    Maya(Autodesk Maya) Windows版

    Autodesk Maya 是面向影视、电视、动画和游戏行业的专业三维制作软件,集多边形与 NURBS 建模、角色绑定、关键帧和动作捕捉动画、Bifrost 特效模拟、材质开发及 Arnold 渲染于一体,

    Aperty Windows版
    Aperty Windows版

    Aperty 是 Skylum 推出的 AI 人像修图软件,支持自然肤质修饰、瑕疵处理、面部与身体调整、预设及批量同步。

    火山引擎 网页版
    火山引擎 网页版

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    立刻MV 网页版
    立刻MV 网页版

    立刻MV 面向音乐人、内容创作者和品牌团队,帮助用户低成本完成歌曲 MV、歌词视频、AI 分镜、图生视频与最终合成。

    蛙蛙写作 网页版
    蛙蛙写作 网页版

    蛙蛙写作 AI 创作工具,覆盖小说、剧本、文案,一键生成文稿,打通文字转视频全流程,高效解决创作难题。

    讯飞智作 网页版
    讯飞智作 网页版

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    超级简历WonderCV 网页版
    超级简历WonderCV 网页版

    超级简历WonderCV是面向应届生和AI小白的智能求职平台,覆盖简历、面试与职业规划。

    音述AI 网页版
    音述AI 网页版

    音述AI,是一个以“用声音述说故事”为核心的 AI 音乐创作与声音分享社区。

    AionClaw Windows版
    AionClaw Windows版

    AionClaw 是可在桌面端运行的任务型 AI 助手,通过数字专家与技能市场组合办公、创作、数据和开发能力。

    Loomy Windows版
    Loomy Windows版

    Loomy 是科大讯飞推出的桌面 AI 助手,可通过对话、任务与工作目录协助处理办公、内容创作和资料整理。

    AirServer Windows版
    AirServer Windows版

    AirServer Windows版可把电脑变成AirPlay、Google Cast和Miracast无线投屏接收端,适合会议演示、课堂教学及家庭大屏分享。发送设备通常无需另装客户端,并可同时接收和智能排列多个镜像会话。