Javamail NTLM 身份验证失败

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4337812/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-14 16:06:06  来源:igfitidea点击:

Javamail NTLM Authentication Failure

javaexchange-serverjavamailntlm

提问by Sam Barnum

Trying to connect to Exchange server using NTLM in JavaMail. I can connect to SMTP, but not IMAP. I can also authenticate via the OS X Mail.app application using the identical host/username/password, account type = "IMAP", Port 143, ssl=false, authentication=NTLM, Domain Name="".

尝试在 JavaMail 中使用 NTLM 连接到 Exchange 服务器。我可以连接到 SMTP,但不能连接到 IMAP。我还可以使用相同的主机/用户名/密码、帐户类型 = "IMAP"、端口 143、ssl=false、authentication=NTLM、Domain Name="" 通过 OS X Mail.app 应用程序进行身份验证。

The connecting code:

连接代码:

import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.Store;
import java.util.Properties;

    public class NTLMTest {
        public static void main(String[] args) throws Exception {
            final String host = "example.com";
            final String user = "bob";
            final String password = "password";

            final Properties properties = new Properties();
            Session session = Session.getDefaultInstance(properties);
            session.setDebug(true);

            // SMTP CONNECT
            final Transport transport = session.getTransport("smtp");
            transport.connect(host, user, password);
            System.out.println("SMTP Connect successful");

            // IMAP CONNECT
            final Store store = session.getStore("imap");
            store.connect(host, user, password);
            System.out.println("IMAP Connect Successful");

        }
    }

The output:

输出:

DEBUG: setDebug: JavaMail version 1.4.3
DEBUG: getProvider() returning javax.mail.Provider[TRANSPORT,smtp,com.sun.mail.smtp.SMTPTransport,Sun Microsystems, Inc]
DEBUG SMTP: useEhlo true, useAuth false
DEBUG SMTP: trying to connect to host "example.com", port 25, isSSL false
220 server18.example.com ESMTP Sendmail 8.14.3/8.14.3/Debian-5+lenny1; Thu, 2 Dec 2010 18:05:30 +0100; (No UCE/UBE) logging access from: xxx.xxx.xxx.xxx
DEBUG SMTP: connected to host "example.com", port: 25

EHLO 192.168.1.107
250-server18.example.com Hello c-xxxx [xxx.xxx.xxx.xxx], pleased to meet you
250-ENHANCEDSTATUSCODES
250-PIPELINING
250-8BITMIME
250-SIZE 20971520
250-DSN
250-ETRN
250-AUTH DIGEST-MD5 CRAM-MD5 LOGIN PLAIN
250-STARTTLS
250-DELIVERBY
250 HELP
DEBUG SMTP: Found extension "ENHANCEDSTATUSCODES", arg ""
DEBUG SMTP: Found extension "PIPELINING", arg ""
DEBUG SMTP: Found extension "8BITMIME", arg ""
DEBUG SMTP: Found extension "SIZE", arg "20971520"
DEBUG SMTP: Found extension "DSN", arg ""
DEBUG SMTP: Found extension "ETRN", arg ""
DEBUG SMTP: Found extension "AUTH", arg "DIGEST-MD5 CRAM-MD5 LOGIN PLAIN"
DEBUG SMTP: Found extension "STARTTLS", arg ""
DEBUG SMTP: Found extension "DELIVERBY", arg ""
DEBUG SMTP: Found extension "HELP", arg ""
DEBUG SMTP: Attempt to authenticate
DEBUG SMTP: check mechanisms: LOGIN PLAIN DIGEST-MD5 NTLM 
AUTH LOGIN
334 VXNlcm5hbWU6
YWR2aWVzZW5raWVzMDU=
334 UGFzc3dvcmQ6
ZGlja2hvbmluZw==
235 2.0.0 OK Authenticated
SMTP Connect successful
DEBUG: getProvider() returning javax.mail.Provider[STORE,imap,com.sun.mail.imap.IMAPStore,Sun Microsystems, Inc]
DEBUG: mail.imap.fetchsize: 16384
DEBUG: mail.imap.statuscachetimeout: 1000
DEBUG: mail.imap.appendbuffersize: -1
DEBUG: mail.imap.minidletime: 10
DEBUG: trying to connect to host "example.com", port 143, isSSL false
* OK server18.example.com Cyrus IMAP4 v2.1.18-IPv6-Debian-2.1.18-5.1 server ready
A0 CAPABILITY
* CAPABILITY IMAP4 IMAP4rev1 ACL QUOTA LITERAL+ MAILBOX-REFERRALS NAMESPACE UIDPLUS ID NO_ATOMIC_RENAME UNSELECT CHILDREN MULTIAPPEND SORT THREAD=ORDEREDSUBJECT THREAD=REFERENCES IDLE AUTH=DIGEST-MD5 AUTH=NTLM AUTH=CRAM-MD5 ANNOTATEMORE
A0 OK Completed
IMAP DEBUG: AUTH: DIGEST-MD5
IMAP DEBUG: AUTH: NTLM
IMAP DEBUG: AUTH: CRAM-MD5
DEBUG: protocolConnect login, host=example.com, user=bob, password=<non-null>
DEBUG NTLM: type 1 message: Type1Message[suppliedDomain=,suppliedWorkstation=192.168.1.107,flags=0x00000201]
DEBUG NTLM: type 1 message length: 45
A1 AUTHENTICATE NTLM
+ 
TlRMTVNTUAABAAAAASIAAAAAAAAAAAAADQANACAAAAAxOTIuMTY4LjEuMTA3
+ TlRMTVNTUAACAAAAAAAAADAAAAABIgAApdhJrA6NzmwAAAAAAAAAAAAAAAAAAAAA
TlRMTVNTUAADAAAAGAAYAEAAAAAwADAAWAAAAAAAAAAAAAAAHAAcAIgAAAAaABoApAAAAAAAAAAAAAAAAQIAALV6mIutJKdZSH4IZGmvNqNFxJafzInd0yJDR4J3oe3LyBls0Y75UuwBAQAAAAAAANAS9yNDkssBVbH5v087iUIAAAAAAAAAAGEAZAB2AGkAZQBzAGUAbgBrAGkAZQBzADAANQAxADkAMgAuADEANgA4AC4AMQAuADEAMAA3AA==
A1 NO authentication failure
Exception in thread "main" javax.mail.AuthenticationFailedException: authentication failure
    at com.sun.mail.imap.IMAPStore.protocolConnect(IMAPStore.java:613)
    at javax.mail.Service.connect(Service.java:291)
    at javax.mail.Service.connect(Service.java:172)
    at com.prosc.emailplugin.NTLMTest.main(NTLMTest.java:25)
Disconnected from the target VM, address: '127.0.0.1:56125', transport: 'socket'

Process finished with exit code 1

I tried wrapping the username with backslashes, per http://www.oracle.com/technetwork/java/faq-135477.html#Exchange-loginI get the following error:

我尝试用反斜杠包装用户名,根据http://www.oracle.com/technetwork/java/faq-135477.html#Exchange-login我收到以下错误:

Exception in thread "main" javax.mail.AuthenticationFailedException: One time use of a plaintext password will enable requested mechanism for user

Backslashes around the username in the SMTP connect portion cause it to fail. I can't tell if the "One time use" error is a step in the right direction or not.

SMTP 连接部分中用户名周围的反斜杠会导致它失败。我不知道“一次性使用”错误是否是朝着正确方向迈出的一步。

采纳答案by FelixJongleur42

I have noticed that - via SMTP - the NTLM Authentication did not work with an older version of javax.mail (up to 1.4.1) but it now works with version 1.4.5. And the user name to be specified was of the form "domain\username". Could be that same effect (difference in versions of javax.mail) would also apply to IMAP.

我注意到 - 通过 SMTP - NTLM 身份验证不适用于较旧版本的 javax.mail(最高 1.4.1),但它现在适用于 1.4.5 版。并且要指定的用户名的格式为“域\用户名”。可能同样的效果(javax.mail 版本的差异)也适用于 IMAP。

回答by Jon Ander Ortiz Durántez

Try setting the domain "" to the properties of the imap store :

尝试将域“”设置为 imap 存储的属性:

properties.setProperty("mail.imap.auth.ntlm.domain","");

Since in SMTP you are login using LOGIN, the use of the domain is not necessary. But in NTLM the domain is mandatory.

由于在 SMTP 中您使用 LOGIN 登录,因此不需要使用域。但在 NTLM 中,域是强制性的。

回答by Asaf Mesika

From what I recall about NTLM, and your NTLM debug messages I can gather the following:

根据我对 NTLM 的回忆以及您的 NTLM 调试消息,我可以收集以下信息:

  • NTLM was designed for single sign on, thus taking credentials from the windows machine its running on - especially the JDK implementation of NTLM.
    • NTLM has two versions, well three to be exact. NTLM v1, NTLMv2 and another version I can't recall at the moment. NTLM v1 has a security hole which allows you to really use a username and password and connect using the NTLM protocol. In NTLM v2 it was fixed, which forces the implementation to take the password (the hashed pass) from the logged in Windows machine.
    • It seems that the NTLM protocol in your case stops after the first message sent by the Exchange server. Notice it has flags which declares which type of NTLM is used and other such as : Encryption, etc.
  • NTLM 是为单点登录而设计的,因此从运行它的 Windows 机器上获取凭据 - 特别是 NTLM 的 JDK 实现。
    • NTLM 有两个版本,确切地说是三个版本。NTLM v1、NTLMv2 和另一个我现在想不起来的版本。NTLM v1 有一个安全漏洞,它允许您真正使用用户名和密码并使用 NTLM 协议进行连接。在 NTLM v2 中,它是固定的,这会强制实现从登录的 Windows 机器获取密码(散列密码)。
    • 您的情况下的 NTLM 协议似乎在 Exchange 服务器发送第一条消息后停止。请注意,它具有声明使用哪种类型的 NTLM 和其他类型的标志,例如:加密等。

I suggest you try following the road in which the credentials (u/p) are taken automatically by the JDK from the client Windows machine.

我建议您尝试遵循 JDK 自动从客户端 Windows 机器获取凭据 (u/p) 的方法。

回答by Eric Leschinski

Here is my complete working solution:

这是我完整的工作解决方案:

Using netbeans, create a new java application project. Put this code in there:

使用 netbeans,创建一个新的 java 应用程序项目。把这段代码放在那里:

package javaapplication4;
import java.util.Date;
import java.util.Properties;

import javax.mail.Message;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.MimeMessage;


public class JavaApplication4 {
    public static void main(String[] args) throws Exception {
           new JavaApplication4().send_email();
    }
    private void send_email() throws Exception
    {
        Properties props = new Properties();
        props.put("mail.smtp.host", "smtp.yourserver.net");
        props.put("mail.from", "[email protected]");
        props.put("mail.smtp.starttls.enable", "true");
        props.put("mail.smtp.ssl.enable", "false");
        props.put("mail.smtp.auth", "true");
        props.put("mail.smtp.port", "587");

        Authenticator authenticator = new Authenticator();
        props.setProperty("mail.smtp.submitter", authenticator.getPasswordAuthentication().getUserName());

        Session session = Session.getInstance(props, authenticator);
        MimeMessage msg = new MimeMessage(session);
        msg.setFrom();
        msg.setRecipients(Message.RecipientType.TO, "[email protected]");  
            // also tried @gmail.com
        msg.setSubject("JavaMail ssl test");
        msg.setSentDate(new Date());
        msg.setText("Hello, world!\n");

        Transport transport;
        transport = session.getTransport("smtp");
        transport.connect();
        msg.saveChanges(); 
        transport.sendMessage(msg, msg.getAllRecipients());
        transport.close();
    }
    private class Authenticator extends javax.mail.Authenticator {
       private PasswordAuthentication authentication;
       public Authenticator() {
           String username = "[email protected]";
           String password = "yourpassword";
           authentication = new PasswordAuthentication(username, password);
       }
       protected PasswordAuthentication getPasswordAuthentication() {
           return authentication;
       }
   }
}

Change the username, passwords, ports, and properties to your specific settings.

将用户名、密码、端口和属性更改为您的特定设置。

You will need to get the javamail-1.4.7and load the mail.jarfrom (http://www.oracle.com/technetwork/java/index-138643.html) into the project.

您需要从 ( http://www.oracle.com/technetwork/java/index-138643.html)获取javamail-1.4.7并加载到项目中。 mail.jar

One thing we did which shed light on what our parameters should be is to download Thunderbird mail client which can auto-discover information about the exchange server to make sure all of our settings were right. If you cant convince thunderbird to connect, then this code likewise shouldn't be expected to work.

我们做的一件事阐明了我们的参数应该是什么是下载 Thunderbird 邮件客户端,它可以自动发现有关交换服务器的信息,以确保我们的所有设置都是正确的。如果您无法说服 Thunderbird 进行连接,那么同样不应期望此代码有效。

回答by Andrea Luciano

I had the same problem sending email via Exhange SMTP connector. After finding out that javamail does not support NTLMv2authentication, I worked out a workaround that requires JCIFS library though.

我在通过 Exhange SMTP 连接器发送电子邮件时遇到了同样的问题。在发现 javamail 不支持NTLMv2身份验证后,我找到了一个需要 JCIFS 库的解决方法。

I downloded the javamail api source code (https://java.net/projects/javamail/pages/Home) and edited the class com.sun.mail.auth.Ntlm to add the missing support for NTLMv2 using the JCIFS library support (http://jcifs.samba.org).

我下载了 javamail api 源代码 ( https://java.net/projects/javamail/pages/Home) 并编辑类 com.sun.mail.auth.Ntlm 以使用 JCIFS 库支持添加对 NTLMv2 缺少的支持 ( http://jcifs.samba.org)。

The first modification in file Ntlm.java was in method init0, and consisted of adding the missing flag NTLMSSP_NEGOTIATE_NTLM2:

文件 Ntlm.java 中的第一个修改是在方法 init0 中,包括添加缺少的标志NTLMSSP_NEGOTIATE_NTLM2

private void init0() {
// ANDREA LUCIANO:
//    turn on the NTLMv2 negotiation flag in TYPE1 messages: 
//    NTLMSSP_NEGOTIATE_NTLM2   (0x00080000) 
//  See also http://davenport.sourceforge.net/ntlm.html#type1MessageExample

    type1 = new byte[256];
    type3 = new byte[256];
    System.arraycopy(new byte[] {'N','T','L','M','S','S','P',0,1}, 0,
            type1, 0, 9);
    type1[12] = (byte) 3;
    type1[13] = (byte) 0xb2;
    type1[14] = (byte) 0x08;  // ANDREA LUCIANO - added
// ...

The second modification was to replace the method generateType3Msg with this:

第二个修改是用以下方法替换 generateType3Msg 方法:

public String generateType3Msg(String challenge) {
    /* First decode the type2 message */
    byte[] type2 = null;
    try {
       type2 = BASE64DecoderStream.decode(challenge.getBytes("us-ascii"));
       logger.fine("type 2 message: " + toHex(type2)); // ANDREA LUCIANO - added
    } catch (UnsupportedEncodingException ex) {
       // should never happen
       assert false;
    }
    jcifs.ntlmssp.Type2Message t2m;
    try {
          t2m = new jcifs.ntlmssp.Type2Message(type2);
    } catch (IOException ex) {
          logger.log(Level.FINE, "Invalid Type 2 message", ex);
          return "";   // will fail later
    }

    final int type2Flags = t2m.getFlags();
    final int type3Flags = type2Flags & (0xffffffff ^ (jcifs.ntlmssp.NtlmFlags.NTLMSSP_TARGET_TYPE_DOMAIN | jcifs.ntlmssp.NtlmFlags.NTLMSSP_TARGET_TYPE_SERVER));

    jcifs.ntlmssp.Type3Message t3m = new jcifs.ntlmssp.Type3Message(t2m, password, ntdomain, username, hostname, type3Flags);
       return jcifs.util.Base64.encode(t3m.toByteArray());
}

The simpest way I found to patch the library is to compile the class and update the library jar file:

我发现修补库的最简单方法是编译类并更新库 jar 文件:

"c:\Program Files\Java\jdk1.5.0_22\bin\javac.exe" -cp jcifs-1.3.17.jar;javax.mail-1.5.2.jar -d . Ntlm.java 
 jar uvf javax.mail-1.5.2.jar com\sun\mail\auth\Ntlm.class

To enable debug as much as possible, I used the following code in the main method of my test class:

为了尽可能多地启用调试,我在测试类的 main 方法中使用了以下代码:

    final InputStream inputStream = Main.class.getResourceAsStream("/logging.properties");
    LogManager.getLogManager().readConfiguration(inputStream);

    Properties props = new Properties();

    props.put("mail.debug", "true");
    props.put("mail.debug.auth", "true");

with this logging.properties:

使用这个 logging.properties:

   # Logging
   handlers = java.util.logging.ConsoleHandler

  .level = INFO

  # Console Logging
  java.util.logging.ConsoleHandler.level = INFO

Before applying the patch the test was stuck after sending Type 1 message, because my Exchange server required NTLMv2 authentication. After the patch the athentication was done successfully.

在应用补丁之前,发送类型 1 消息后测试卡住了,因为我的 Exchange 服务器需要 NTLMv2 身份验证。补丁后,验证成功完成。

Another solution is to send email via the ##Exchange webservice## aka EWS by using the ##EWS Java API## released and mantained by Microsoft (https://github.com/OfficeDev/ews-java-api), such as in this example:

另一种解决方案是使用 Microsoft ( https://github.com/OfficeDev/ews-java-api)发布和维护的 ##EWS Java API## 通过 ##Exchange webservice## aka EWS 发送电子邮件,例如如本例所示:

public class Main {

/**
 * @param args
 */
public static void main(String[] args) throws Exception {

       ExchangeService exchangeService = new ExchangeService(ExchangeVersion.Exchange2007_SP1);

        ExchangeCredentials credentials = new WebCredentials("myusername","mypassword", "mydomain");

        exchangeService.setCredentials(credentials);
        exchangeService.setUrl(new URI("https://myhostname/EWS/Exchange.asmx"));

        exchangeService.setTraceEnabled(true);

        EmailMessage msg;
        msg = new EmailMessage(exchangeService);
        msg.setFrom(new EmailAddress("[email protected]"));
        msg.setSubject("Test Mail");
        msg.getToRecipients().add("[email protected]");
        msg.setBody(MessageBody.getMessageBodyFromText("Email sent by Miscrosoft Java EWS API."));
        msg.getAttachments().addFileAttachment("c:\temp\myattachement.pdf");
        msg.send();

}

}

}

But again there is a patch to apply in the inner class NTLM of EwsJCIFSNTLMScheme.java to enable NTLMv2 as indicated in the answer to this post:

但是同样有一个补丁可以应用于 EwsJCIFSNTLMScheme.java 的内部类 NTLM 以启用 NTLMv2,如这篇文章的答案所示:

How to use LDAP authentication for the Exchange Web Services connection in Java?

如何在 Java 中对 Exchange Web 服务连接使用 LDAP 身份验证?

That is:

那是:

private class NTLM {
/** Character encoding */
public static final String DEFAULT_CHARSET = "ASCII";

/**
* The character was used by 3.x's NTLM to encode the username and
* password. Apparently, this is not needed in when passing username,
* password from NTCredentials to the JCIFS library
*/
private String credentialCharset = DEFAULT_CHARSET;

void setCredentialCharset(String credentialCharset) {
       this.credentialCharset = credentialCharset;
}

private static final int TYPE_1_FLAGS = NtlmFlags.NTLMSSP_NEGOTIATE_NTLM
             | NtlmFlags.NTLMSSP_NEGOTIATE_UNICODE
             | NtlmFlags.NTLMSSP_NEGOTIATE_NTLM2;

private String generateType1Msg(String host, String domain) {
       jcifs.ntlmssp.Type1Message t1m = new jcifs.ntlmssp.Type1Message(
                    TYPE_1_FLAGS, domain, host);
       return jcifs.util.Base64.encode(t1m.toByteArray());
}

private String generateType3Msg(String username, String password,
             String host, String domain, String challenge) {
       jcifs.ntlmssp.Type2Message t2m;
       try {
             t2m = new jcifs.ntlmssp.Type2Message(
                           jcifs.util.Base64.decode(challenge));
       } catch (IOException e) {
             throw new RuntimeException("Invalid Type2 message", e);
       }

       final int type2Flags = t2m.getFlags();
       final int type3Flags = type2Flags
                    & (0xffffffff ^ (NtlmFlags.NTLMSSP_TARGET_TYPE_DOMAIN | NtlmFlags.NTLMSSP_TARGET_TYPE_SERVER));

       jcifs.ntlmssp.Type3Message t3m = new jcifs.ntlmssp.Type3Message(
                    t2m, password, domain, username, host, type3Flags);
       return jcifs.util.Base64.encode(t3m.toByteArray());
}

}

}

I tried and it worked for me.

我试过了,它对我有用。