Javamail NTLM身份验证失败

时间:2010-12-02 17:19:14

标签: java exchange-server javamail ntlm

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

连接代码:

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");

        }
    }

输出:

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

我尝试使用反斜杠包装用户名,每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

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

5 个答案:

答案 0 :(得分:8)

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

答案 1 :(得分:2)

从我记得的NTLM和你的NTLM调试消息我可以收集以下内容:

  • NTLM专为单点登录而设计,因此从运行的Windows机器获取凭据 - 尤其是NTLM的JDK实现。
    • NTLM有两个版本,确切地说是三个版本。 NTLM v1,NTLMv2和我目前无法回想起的另一个版本。 NTLM v1有一个安全漏洞,允许您真正使用用户名和密码并使用NTLM协议进行连接。在NTLM v2中它被修复,这迫使实现从登录的Windows机器中获取密码(散列传递)。
    • 您的案例中的NTLM协议似乎在Exchange服务器发送的第一条消息之后停止。请注意,它有标志,用于声明使用哪种类型的NTLM,以及其他如:加密等。

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

答案 2 :(得分:1)

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

使用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", "yourusername@youremailaddress.com");
        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, "yourusername@youremailaddress.com");  
            // 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 = "yourusername@youremailaddress.com";
           String password = "yourpassword";
           authentication = new PasswordAuthentication(username, password);
       }
       protected PasswordAuthentication getPasswordAuthentication() {
           return authentication;
       }
   }
}

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

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

我们所做的一件事就是明确我们的参数应该是下载Thunderbird邮件客户端,它可以自动发现有关Exchange服务器的信息,以确保我们的所有设置都正确。如果你不能说服雷鸟连接,那么这个代码也不应该起作用。

答案 3 :(得分:1)

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

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

文件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
// ...

第二个修改是用以下方法替换方法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());
}

我发现修补库的最简单的方法是编译类并更新库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

为了尽可能地启用调试,我在测试类的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");

使用此logging.properties:

   # Logging
   handlers = java.util.logging.ConsoleHandler

  .level = INFO

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

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

另一个解决方案是使用## EWS Java API ##通过## Exchange Webservice ## aka EWS发送电子邮件,由Microsoft(https://github.com/OfficeDev/ews-java-api)发布并保留,例如在此示例中:

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("myuser@myserver.com"));
        msg.setSubject("Test Mail");
        msg.getToRecipients().add("myuser@gmail.com");
        msg.setBody(MessageBody.getMessageBodyFromText("Email sent by Miscrosoft Java EWS API."));
        msg.getAttachments().addFileAttachment("c:\\temp\\myattachement.pdf");
        msg.send();

}

}

但是再次在EwsJCIFSNTLMScheme.java的内部类NTLM中应用了一个补丁来启用NTLMv2,如本帖的答案所示:

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

那是:

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());
}

}

我试过,它对我有用。

答案 4 :(得分:-1)

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

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

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