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

我正在尝试使用Apache / Jakarta HttpClient 4.1.1使用给定的凭据连接到任意网页。 为了测试这一点,我在运行的dev机器上安装了最少的IIS 7.5,其中一次只有一种身份validation模式处于活动状态。 基本身份validation工作正常,但每当我尝试登录时,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 authtypes = new ArrayList(); 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提供更强的哈希值? 有任何想法吗? 如果我能够validation这适用于NTLM,那就太棒了。 摘要也会很好,但如果有必要,我可以不用它。

我不是这方面的专家,但在使用http组件的NTLM身份validation期间,我看到客户端需要3次尝试才能连接到我的情况下的NTML端点。 这里有针对Spnego的描述,但它与NTLM身份validation略有不同。

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

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

第二次尝试,客户端将使用Target auth state: CHALLENGED发出请求,并将发送一个带有以base64格式编码的令牌的授权头: Authorization: NTLM TlRMTVNTUAABAAAAAYIIogAAAAAoAAAAAAAAACgAAAAFASgKAAAADw==现在填充了编码信息。

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

在我的情况下,我收到HTTP/1.1 200 OK

为了避免在第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(); 

我遇到了与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 authtypes = new ArrayList(); 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(); 

我遇到了与HttpClient 4.1.2类似的问题。 对我来说,通过恢复到HttpClient 4.0.3解决了这个问题。 我无法使用内置实现或使用JCIFS让NTLM使用4.1.2。

我发现解决这种情况的最简单方法是Wireshark 。 这是一个非常大的锤子,但它真的会向你展示一切。 安装它,确保您的服务器在另一台机器上(不能与Localhost一起使用)并开始记录。

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

我用这种方法解决了令人难以置信的问题。 而Wireshark是非常有用的工具。 许多超级高级function使您的网络调试更容易。

您也可以在客户端或服务器端运行它。 无论什么会向您展示允许您进行比较的两个请求。

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

我终于弄明白了。 摘要式身份validation要求如果在请求中使用完整URL,则代理还需要使用完整URL。 我没有在代码中留下代理代码,但它被定向到“localhost”,导致它失败。 将其更改为127.0.0.1使其工作。