【问题标题】:Javamail NTLM Authentication FailureJavamail NTLM 身份验证失败
【发布时间】:2011-05-19 06:40:49
【问题描述】:

尝试使用 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 连接部分中用户名周围的反斜杠会导致连接失败。我不知道“一次性使用”错误是否是朝着正确方向迈出的一步。

【问题讨论】:

  • 根据 javamail FAQ,您应该使用 \bob\bob(假设“bob”既是 NT 用户名又是邮件地址)。这是您在遇到“一次性使用”错误时尝试的方法吗?
  • 如果您可以嗅探 Mail.app 与 Exchange Server 的连接,您可能会看到它是如何进行身份验证的(尽管您不知道如何生成令牌,但您可能会看到它是否在做任何不同的事情来自 JavaMail)。
  • @Chochos,我从来没有被要求为 Mail.app 输入 NT 用户名。我也尝试使用用户名,但得到同样的错误。
  • @Chochos,我尝试启动 TCPMonitor 以侦听 localhost:9143 并将请求转发到端口 143 上的服务器,但我得到的是:* OK server18.example.nl Cyrus IMAP4 v2.1.18 -IPv6-Debian-2.1.18-5.1 server ready 除了143还有其他端口在用吗?
  • IMAP 应该只有 143...

标签: java exchange-server jakarta-mail ntlm


【解决方案1】:

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

【讨论】:

    【解决方案2】:

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

    • NTLM 是为单点登录而设计的,因此可以从运行它的 Windows 机器上获取凭据——尤其是 NTLM 的 JDK 实现。
      • NTLM 有两个版本,确切地说是三个。 NTLM v1、NTLMv2 和另一个我现在想不起来的版本。 NTLM v1 有一个安全漏洞,允许您真正使用用户名和密码并使用 NTLM 协议进行连接。在 NTLM v2 中,该问题已得到修复,这会强制实现从登录的 Windows 机器中获取密码(散列密码)。
      • 您的 NTLM 协议似乎在 Exchange 服务器发送第一条消息后停止。请注意,它有声明使用哪种类型的 NTLM 和其他标志,例如:加密等。

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

    【讨论】:

    • 如果我从 Windows 以外的地方运行?我还能向 NTLM 服务器进行身份验证吗?
    • 很可能不是,如果我们谈论的是 NTLMv2。 BTW:我只记得我们当时采用的解决方案是使用 Kerberos。大多数交换都支持 Kerberos 身份验证,协议支持凭据委托,我认为如果您从非 Windows 站点浏览它也可以工作。
    【解决方案3】:

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

    使用 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.jar 从 (http://www.oracle.com/technetwork/java/index-138643.html) 加载到项目中。

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

    【讨论】:

    • 问题是关于IMAP,没有SMTP认证
    【解决方案4】:

    我在通过 Exchange 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 身份验证。 打补丁后认证成功。

    另一种解决方案是使用由 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("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());
    }
    

    }

    我试过了,它对我有用。

    【讨论】:

      【解决方案5】:

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

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

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

      【讨论】:

      • 嗨乔恩,没有运气,同样的结果。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-01-10
      • 2013-01-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多