简体   繁体   English

Javamail NTLM身份验证失败

[英]Javamail NTLM Authentication Failure

Trying to connect to Exchange server using NTLM in JavaMail. 尝试在JavaMail中使用NTLM连接到Exchange服务器。 I can connect to SMTP, but not IMAP. 我可以连接到SMTP,但不能连接到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="". 我还可以使用相同的主机/用户名/密码,帐户类型=“IMAP”,端口143,ssl = false,身份验证= NTLM,域名=“”通过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-login I 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. SMTP连接部分中用户名周围的反斜杠会导致其失败。 I can't tell if the "One time use" error is a step in the right direction or not. 我不知道“一次性使用”错误是否是朝着正确方向迈出的一步。

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. 我注意到 - 通过SMTP - NTLM身份验证不适用于旧版本的javax.mail(最高1.4.1),但它现在适用于1.4.5版本。 And the user name to be specified was of the form "domain\\username". 并且要指定的用户名的格式为“domain \\ username”。 Could be that same effect (difference in versions of javax.mail) would also apply to IMAP. 可能是同样的效果(javax.mail版本的差异)也适用于IMAP。

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专为单点登录而设计,因此从运行的Windows机器获取凭据 - 尤其是NTLM的JDK实现。
    • NTLM has two versions, well three to be exact. NTLM有两个版本,确切地说是三个版本。 NTLM v1, NTLMv2 and another version I can't recall at the moment. NTLM v1,NTLMv2和我目前无法回想起的另一个版本。 NTLM v1 has a security hole which allows you to really use a username and password and connect using the NTLM protocol. NTLM v1有一个安全漏洞,允许您真正使用用户名和密码并使用NTLM协议进行连接。 In NTLM v2 it was fixed, which forces the implementation to take the password (the hashed pass) from the logged in Windows machine. 在NTLM v2中它被修复,这迫使实现从登录的Windows机器获取密码(散列传递)。
    • It seems that the NTLM protocol in your case stops after the first message sent by the Exchange server. 在您的情况下,NTLM协议似乎在Exchange服务器发送的第一条消息之后停止。 Notice it has flags which declares which type of NTLM is used and other such as : Encryption, etc. 请注意,它有标志,用于声明使用哪种类型的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)的道路。

Here is my complete working solution: 这是我完整的工作解决方案:

Using netbeans, create a new java application project. 使用netbeans,创建一个新的Java应用程序项目。 Put this code in there: 把这段代码放在那里:

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

Change the username, passwords, ports, and properties to your specific settings. 将用户名,密码,端口和属性更改为您的特定设置。

You will need to get the javamail-1.4.7 and load the mail.jar from ( http://www.oracle.com/technetwork/java/index-138643.html ) into the project. 您需要获取javamail-1.4.7并将mail.jar从( http://www.oracle.com/technetwork/java/index-138643.html )加载到项目中。

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. 我们所做的一件事就是明确我们的参数应该是下载Thunderbird邮件客户端,它可以自动发现有关Exchange服务器的信息,以确保我们所有的设置都是正确的。 If you cant convince thunderbird to connect, then this code likewise shouldn't be expected to work. 如果你不能说服thunderbird连接,那么这个代码同样不应该起作用。

I had the same problem sending email via Exhange SMTP connector. 我通过Exhange SMTP连接器发送电子邮件时遇到同样的问题。 After finding out that javamail does not support NTLMv2 authentication, I worked out a workaround that requires JCIFS library though. 在发现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. 在应用修补程序之前,测试在发送Type 1消息后被卡住,因为我的Exchange服务器需要NTLMv2身份验证。 After the patch the athentication was done successfully. 在补丁之后,成功完成了身份验证。

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

}

} }

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中使用LDAP身份验证进行Exchange Web服务连接?

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. 我试过,它对我有用。

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. 由于在SMTP中您使用LOGIN登录,因此不需要使用域。 But in NTLM the domain is mandatory. 但在NTLM中,域名是强制性的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM