Javamail NTLM身份validation失败

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

连接代码:

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

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

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

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

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

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

使用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邮件客户端,它可以自动发现有关Exchange服务器的信息,以确保我们的所有设置都正确。 如果你不能说服thunderbird连接,那么这个代码同样不应该起作用。

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

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

文件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身份validation。 在补丁之后,成功完成了身份validation。

另一种解决方案是使用## EWS Java API ##通过## Exchange Webservice ## aka EWS发送电子邮件,这些#API由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,如本文的回答中所示:

如何在Java中使用LDAP身份validation进行Exchange Web服务连接?

那是:

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

}

我试过,它对我有用。

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

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

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