ntlm 身份验证的 Java URLConnection 错误,但仅在 Linux 和 Java 7 上

Posted

技术标签:

【中文标题】ntlm 身份验证的 Java URLConnection 错误,但仅在 Linux 和 Java 7 上【英文标题】:Java URLConnection error with ntlm authentication, but only on Linux and only Java 7 【发布时间】:2013-02-13 23:08:25 【问题描述】:

我正在尝试打开一个到受 NTLM 身份验证方案保护的 url 的 http 连接。当我们在 Java 6 上时,这段代码已经正常工作了 2 年。我编写了一个小型 Java 程序,它可以访问该特定 url 以使测试用例尽可能简单。

问题是我无法使程序在 linux 上运行,并且在使用 JDK 7 版本时。Java 尝试 20 次访问 URL,然后我收到一个错误,告诉我服务器重定向太多次。它适用于 linux 和 JDK 6,以及适用于 JDK 6 或 7 的 windows 7。

我检查并尝试了此处列出的解决方案(以及许多其他解决方案):Getting "java.net.ProtocolException: Server redirected too many times" Error。它没有用。我还必须补充一点,从浏览器访问 url 时,我可以看到没有涉及 cookie。

这是我尝试过的 os/java 版本的详细信息:

成功:

Windows 7:Java(TM) SE 运行时环境(内部版本 1.7.0_15-b03)(64 位) Windows 7:Java(TM) SE 运行时环境(内部版本 1.7.0_10-b18)(64 位) Windows 7:Java(TM) SE 运行时环境(内部版本 1.6.0_33-b04)(64 位) Redhat 企业版 linux 6.4:Java(TM) SE 运行时环境(内部版本 1.6.0_33-b04)(64 位)

失败:

Redhat 企业版 linux 6.4:Java(TM) SE 运行时环境(内部版本 1.7.0-b147)(64 位) Redhat 企业版 linux 6.4:Java(TM) SE 运行时环境(内部版本 1.7.0_05-b06)(64 位) Redhat 企业版 linux 6.4:Java(TM) SE 运行时环境(内部版本 1.7.0_13-b20)(64 位) Redhat 企业版 linux 6.4:Java(TM) SE 运行时环境(内部版本 1.7.0_15-b03)(64 位)

当程序运行时,我看到所使用的身份验证方法和我尝试下载的文档作为输出:

Scheme:Negotiate
Scheme:ntlm
.... document content ....
Done

当它失败时,我有以下输出:

Scheme:Negotiate
Scheme:ntlm
Scheme:ntlm
Scheme:ntlm
Scheme:ntlm
Scheme:ntlm
Scheme:ntlm
Scheme:ntlm
Scheme:ntlm
Scheme:ntlm
Scheme:ntlm
Scheme:ntlm
Scheme:ntlm
Scheme:ntlm
Scheme:ntlm
Scheme:ntlm
Scheme:ntlm
Scheme:ntlm
Scheme:ntlm
Scheme:ntlm
Scheme:ntlm
java.net.ProtocolException: Server redirected too many  times (20)
        at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1635)
        at sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:254)
        at TestWs.testWs(TestWs.java:67)
        at TestWs.main(TestWs.java:20)

这是程序的源代码:

package com.test;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Authenticator;
import java.net.CookieHandler;
import java.net.CookieManager;
import java.net.CookiePolicy;
import java.net.PasswordAuthentication;
import java.net.URL;
import java.net.URLConnection;

public class TestWs 

    public static void main(String[] args) throws Exception 
        new TestWs().testWs();
    

    public void testWs() 
        try 
            CookieHandler.setDefault(new CookieManager(null, CookiePolicy.ACCEPT_ALL));
            Authenticator.setDefault(new MyAuthenticator("username", "password"));

            URL url = new URL("https://someurlprotectedbyntlmauthentication.com");
            URLConnection connection = url.openConnection();
            InputStream is = connection.getInputStream();
            InputStreamReader isr = new InputStreamReader(is);
            BufferedReader br = new BufferedReader(isr);
            while (true) 
                String s = br.readLine();
                if (s == null)
                    break;
                System.out.println(s);
            
            System.out.println("Done");
         catch (Exception ex) 
            ex.printStackTrace();
        
    


class MyAuthenticator extends Authenticator 
    private String httpUsername;
    private String httpPassword;

    public MyAuthenticator(String httpUsername, String httpPassword) 
        this.httpUsername = httpUsername;
        this.httpPassword = httpPassword;
    

    @Override
    protected PasswordAuthentication getPasswordAuthentication() 
        System.out.println("Scheme:" + getRequestingScheme());
        return new PasswordAuthentication(httpUsername, httpPassword.toCharArray());
    

任何帮助将不胜感激。

更新:

经过一番调查,我发现如果我使用域用户,身份验证有效,但如果我使用本地用户,则无效。

JDK 7 中的这段代码给我带来了麻烦(com.sun.security.ntlm.Client 类):

public byte[] type3(byte[] type2, byte[] nonce) throws NTLMException 
if (type2 == null || (v != Version.NTLM && nonce == null)) 
throw new NullPointerException("type2 and nonce cannot be null");

debug("NTLM Client: Type 2 received\n");
debug(type2);
Reader r = new Reader(type2);
byte[] challenge = r.readBytes(24, 8);
int inputFlags = r.readInt(20);
boolean unicode = (inputFlags & 1) == 1;
String domainFromServer = r.readSecurityBuffer(12, unicode);
if (domainFromServer != null) 
domain = domainFromServer;

因此,由于服务器已在域中注册,它会将其域作为 NTLM 协议的一部分发送回客户端。 Java 每次都用变量“domainFromServer”替换我试图强制执行的域,但由于用户存在于服务器上而不是服务器的域上,所以它失败了。

我不知道该怎么做。

【问题讨论】:

您使用的是什么 Java 7 版本?在使用 https 或身份验证或安全性时,我遇到了一些问题。我开发的一个应用程序刚刚使用 java 1.7_02 (Java 7.2),但是当我使用最新版本时它不再工作了。 Oracle 在最新版本中特别在安全策略方面做了一些修改。尝试使用 Java 7 的旧版本,例如 1.7_02。 我刚用 Java jdk 版本 1.7.0_02 64 位 linux 试了一下,还是不行,结果一样。 对于 linux(64 位)也是伪造的 1.7.0_25-b15 试过 1.7.0_40 - 64 位在 linux 上失败 - 32 位在 solaris 上失败 - 64 位在 windows7 上成功 【参考方案1】:

我更改了 Client.java 类中的代码,并将其与 com.sun.security.ntlm 包的其余部分一起重新编译,然后我创建了一个名为 rt_fix.jar 的 jar,其中包含该特定包的类。然后我使用了一个 java 启动选项来强制它在内部 rt.jar 之前加载我的 jar。

-Xbootclasspath/p:/path_to_jar/rt_fix.jar

我不喜欢这个解决方案,但它有效。

这是我在 Client.java 中更改的代码,方法 type3:

之前:

    if (domainFromServer != null) 
        domain = domainFromServer;
    

之后:

    if (domainFromServer != null) 
        //domain = domainFromServer;
    

当发送 NTLM 身份验证的第三部分时,它会阻止 Java 更改我尝试使用从服务器收到的域进行身份验证的域。我尝试验证的域实际上是服务器的名称,因为用户帐户是本地的。

【讨论】:

您是否向 Oracle 报告了该错误? 错误报告:bugs.java.com/bugdatabase/view_bug.do?bug_id=8058419。仍标记为打开。【参考方案2】:

我遇到了同样的问题,只是通过指定包含域的用户名来解决它:

    Authenticator.setDefault(new Authenticator() 
        @Override
        protected PasswordAuthentication getPasswordAuthentication() 
            return new PasswordAuthentication(
                    System.getProperty("DOMAIN\\user"),
                    System.getProperty("password").toCharArray() ) ;
        
    );

【讨论】:

【参考方案3】:

正确的是:

Authenticator.setDefault(new Authenticator() 
        @Override
        protected PasswordAuthentication getPasswordAuthentication() 
            return new PasswordAuthentication(
                    "DOMAIN\\user",
                    "password".toCharArray() ) ;
        
    );

【讨论】:

【参考方案4】:

此问题已在 JDK 9-ea 中使用 http://bugs.java.com/view_bug.do?bug_id=7150092 解决,并且该修复程序也使用 http://bugs.java.com/view_bug.do?bug_id=8049690 向后移植到 JDK 8u40

【讨论】:

【参考方案5】:

此错误的另一个可能原因:域用户被阻止

Scheme:ntlm
Scheme:ntlm
java.net.ProtocolException: Server redirected too many  times (20)

当我尝试使用超过域策略允许的错误密码登录时意外阻止了我的域用户,我遇到了同样的错误。

【讨论】:

以上是关于ntlm 身份验证的 Java URLConnection 错误,但仅在 Linux 和 Java 7 上的主要内容,如果未能解决你的问题,请参考以下文章

如何使用球衣发送经过 NTLM 身份验证的发布请求?

HTTP 请求未经客户端身份验证方案“Ntlm”授权

HTTP 请求未使用客户端身份验证方案“Ntlm”未经授权从服务器接收到的身份验证标头为“NTLM”

Python 中的 NTLM 身份验证

Javascript/Ajax NTLM 身份验证

NTLM 和自动匿名身份验证 IIS