HttpClient 4.1.1 在使用 NTLM 进行身份验证时返回 401,浏览器工作正常

Posted

技术标签:

【中文标题】HttpClient 4.1.1 在使用 NTLM 进行身份验证时返回 401,浏览器工作正常【英文标题】:HttpClient 4.1.1 returns 401 when authenticating with NTLM, browsers work fine 【发布时间】:2011-08-20 12:22:03 【问题描述】:

我正在尝试使用 Apache/Jakarta HttpClient 4.1.1 使用给定凭据连接到任意网页。为了测试这一点,我在我的开发机器上安装了一个最小的 IIS 7.5,一次只有一种身份验证模式处于活动状态。基本身份验证工作正常,但每当我尝试登录时,Digest 和 NTLM 都会返回 401 错误消息。这是我的代码:

    DefaultHttpClient httpclient = new DefaultHttpClient();
    HttpContext localContext = new BasicHttpContext();
    HttpGet httpget = new HttpGet("http://localhost/"); 
    CredentialsProvider credsProvider = new BasicCredentialsProvider();
    credsProvider.setCredentials(AuthScope.ANY,
            new NTCredentials("user", "password", "", "localhost"));
    if (!new File(System.getenv("windir") + "\\krb5.ini").exists()) 
        List<String> authtypes = new ArrayList<String>();
        authtypes.add(AuthPolicy.NTLM);
        authtypes.add(AuthPolicy.DIGEST);
        authtypes.add(AuthPolicy.BASIC);
        httpclient.getParams().setParameter(AuthPNames.PROXY_AUTH_PREF,
                authtypes);
        httpclient.getParams().setParameter(AuthPNames.TARGET_AUTH_PREF,
                authtypes);
    
    localContext.setAttribute(ClientContext.CREDS_PROVIDER, credsProvider);
    HttpResponse response = httpclient.execute(httpget, localContext);
    System.out.println("Response code: " + response.getStatusLine());

我在 Fiddler 中注意到的一件事是 Firefox 与 HttpClient 发送的哈希值不同,这让我认为 IIS 7.5 可能期望比 HttpClient 提供的哈希值更强?有任何想法吗?如果我能验证这是否适用于 NTLM,那就太好了。文摘也不错,但如果有必要,我可以不用它。

【问题讨论】:

我获得了 Digest 身份验证以在浏览器中工作,但它仍然显示 401 在 HttpClient 中被禁止。我被难住了。 该代码对我有用,但在 4.3 中已弃用。我找不到使用纯 4.3 代码的明确指南。 【参考方案1】:

我不是该主题的专家,但在使用 http 组件进行 NTLM 身份验证期间,我发现客户端需要 3 次尝试才能连接到我的 NTML 端点。对于 Spnego,它有点描述为 here,但对于 NTLM 身份验证有点不同。

对于 NTLM,客户端将在第一次尝试中使用 Target auth state: UNCHALLENGED 发出请求,并且 Web 服务器返回 HTTP 401 状态和标头:WWW-Authenticate: NTLM

客户端将检查配置的身份验证方案,NTLM 应在客户端代码中配置。

第二次尝试,客户端将使用Target auth state: CHALLENGED 发出请求,并将发送带有以base64 格式编码的令牌的授权标头:Authorization: NTLM TlRMTVNTUAABAAAAAYIIogAAAAAoAAAAAAAAACgAAAAFASgKAAAADw== 服务器再次返回 HTTP 401 状态,但标头:WWW-Authenticate: NTLM 现在填充了编码信息。

第三次尝试客户端将使用来自WWW-Authenticate: NTLM 标头的信息,并将使用Target auth state: HANDSHAKE 和授权标头Authorization: NTLM 发出最终请求,其中包含服务器的更多信息。

就我而言,在那之后我收到了HTTP/1.1 200 OK

为了在每个请求中避免所有这些,documentation 在第 4.7.1 章中声明必须对逻辑相关的请求使用相同的执行令牌。对我来说它没有用。

我的代码: 我在 EJB 的 @PostConstruct 方法中初始化客户端一次

        PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
        cm.setMaxTotal(18);
        cm.setDefaultMaxPerRoute(6);

        RequestConfig requestConfig = RequestConfig.custom()
        .setSocketTimeout(30000)
        .setConnectTimeout(30000)
        .setTargetPreferredAuthSchemes(Arrays.asList(AuthSchemes.NTLM))
        .setProxyPreferredAuthSchemes(Arrays.asList(AuthSchemes.BASIC))
        .build();

        CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
        credentialsProvider.setCredentials(AuthScope.ANY,
                new NTCredentials(userName, password, hostName, domainName));

        // Finally we instantiate the client. Client is a thread safe object and can be used by several threads at the same time. 
        // Client can be used for several request. The life span of the client must be equal to the life span of this EJB.
         this.httpclient = HttpClients.custom()
        .setConnectionManager(cm)
        .setDefaultCredentialsProvider(credentialsProvider)
        .setDefaultRequestConfig(requestConfig)
        .build();

在每个请求中使用相同的客户端实例:

            HttpPost httppost = new HttpPost(endPoint.trim());            
            // HttpClientContext is not thread safe, one per request must be created.
            HttpClientContext context = HttpClientContext.create();    
            response = this.httpclient.execute(httppost, context);

在我的 EJB 的 @PreDestroy 方法中,释放资源并将连接返回给连接管理器:

             this.httpclient.close();

【讨论】:

【参考方案2】:

升级到 HttpClient4.1.X 后,我遇到了同样的问题 HttpClient 4.2.6 它像魅力一样醒来。下面是我的代码

DefaultHttpClient httpclient = new DefaultHttpClient();
        HttpContext localContext = new BasicHttpContext();
        HttpGet httpget = new HttpGet("url"); 
        CredentialsProvider credsProvider = new BasicCredentialsProvider();
        credsProvider.setCredentials(AuthScope.ANY,
                new NTCredentials("username", "pwd", "", "domain"));
                    List<String> authtypes = new ArrayList<String>();
            authtypes.add(AuthPolicy.NTLM);      
            httpclient.getParams().setParameter(AuthPNames.TARGET_AUTH_PREF,authtypes);

        localContext.setAttribute(ClientContext.CREDS_PROVIDER, credsProvider);
        HttpResponse response = httpclient.execute(httpget, localContext);
        HttpEntity entity=response.getEntity();

【讨论】:

【参考方案3】:

我发现解决此类情况的最简单方法是Wireshark。这是一把非常大的锤子,但它真的会告诉你一切。安装它,确保您的服务器在另一台机器上(不适用于 Localhost)并开始记录。

运行失败的请求,运行有效的请求。然后,通过http进行过滤(只需将http放在filter字段中),找到第一个GET请求,找到另一个GET请求并进行比较。识别有意义的差异,您现在有特定的关键字或问题要搜索代码/网络。如果还不够,请缩小到第一个 TCP 会话并查看完整的请求/响应。与另一个相同。

我用这种方法解决了数量惊人的问题。而且 Wireshark 是非常有用的了解工具。大量超高级功能,让您的网络调试更轻松。

您也可以在客户端或服务器端运行它。任何会向您显示两个请求以允许您进行比较的内容。

【讨论】:

【参考方案4】:

我在使用 HttpClient 4.1.2 时遇到了类似的问题。对我来说,它是通过恢复到 HttpClient 4.0.3 来解决的。无论是使用内置实现还是使用 JCIFS,我都无法让 NTLM 与 4.1.2 一起工作。

【讨论】:

请注意,我也遇到了同样的问题。 httpclient 4.2.3 版本声称具有更新的 NTLM 实现。我发现升级到 4.2.3 就 NTLM 而言完美无缺。【参考方案5】:

更新我们的应用程序以使用 httpcomponents-client-4.5.1 中的 jar 为我解决了这个问题。

【讨论】:

【参考方案6】:

我终于明白了。摘要式身份验证要求,如果您在请求中使用完整 URL,则代理也需要使用完整 URL。我没有在示例中留下代理代码,但它被定向到“localhost”,导致它失败。将此更改为 127.0.0.1 即可。

【讨论】:

以上是关于HttpClient 4.1.1 在使用 NTLM 进行身份验证时返回 401,浏览器工作正常的主要内容,如果未能解决你的问题,请参考以下文章

同时使用 SSL 加密和 NTLM 身份验证的 HttpClient 失败

核心中的 NTLM 身份验证 HttpClient

.NET HttpClient 在使用 NTLM 协商时不会在对 IIS 的请求之间保持身份验证

NTLM认证流程详解

在 Axios 中使用 NTLM 身份验证

Windows安全认证是如何进行的?[NTLM篇]