Javamail NTLM身份验证失败
正在尝试使用JavaMail中的NTLM连接到Exchange服务器。我可以连接到SMTP,但不能连接到IMAP。我还可以使用相同的主机/用户名/密码、帐户类型=“IMAP”、端口143、ssl=false、authentication=NTLM、域名=“通过OS X Mail.app应用程序进行身份验证 连接代码:Javamail NTLM身份验证失败,java,exchange-server,jakarta-mail,ntlm,Java,Exchange Server,Jakarta Mail,Ntlm,正在尝试使用JavaMail中的NTLM连接到Exchange服务器。我可以连接到SMTP,但不能连接到IMAP。我还可以使用相同的主机/用户名/密码、帐户类型=“IMAP”、端口143、ssl=false、authentication=NTLM、域名=“通过OS X Mail.app应用程序进行身份验证 连接代码: import javax.mail.Session; import javax.mail.Transport; import javax.mail.Store; import ja
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
SMTP连接部分中用户名周围的反斜杠会导致其失败。我无法判断“一次性使用”错误是否是正确的步骤。尝试将域“”设置为imap存储的属性:
properties.setProperty("mail.imap.auth.ntlm.domain","");
因为在SMTP中,您是使用login登录的,所以不需要使用域。但在NTLM中,域是强制性的 根据我对NTLM的回忆以及您的NTLM调试消息,我可以收集以下信息:
- NTLM是为单点登录而设计的,因此可以从其运行的windows计算机上获取凭据,尤其是NTLM的JDK实现。
- NTLM有两个版本,确切地说是三个。NTLM v1、NTLMv2和另一个版本,我现在想不起来了。NTLM v1有一个安全漏洞,允许您真正使用用户名和密码,并使用NTLM协议进行连接。在NTLM v2中,它是固定的,强制实现从登录的Windows计算机获取密码(哈希传递)
- 您案例中的NTLM协议似乎在Exchange服务器发送第一条消息后停止。请注意,它具有声明使用哪种类型的NTLM的标志,以及其他标志,如:加密等
我建议您尝试遵循JDK从客户端Windows计算机自动获取凭据(u/p)的方法。我注意到,通过SMTP,NTLM身份验证在旧版本的javax.mail(高达1.4.1)中不起作用,但现在在1.4.5版本中起作用。
要指定的用户名的格式为“域\用户名”。同样的效果(javax.mail版本的差异)也适用于IMAP。以下是我的完整工作解决方案: 使用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
从()加载到项目中
我们所做的一件事是下载Thunderbird mail client,它可以自动发现有关exchange服务器的信息,以确保我们的所有设置都是正确的,这对我们的参数应该是什么有帮助的。如果您无法说服thunderbird进行连接,那么这段代码同样不应该起作用。我在通过Exunge SMTP连接器发送电子邮件时也遇到了同样的问题。 在发现javamail不支持NTLMv2身份验证后,我制定了一个需要JCIFS库的解决方案 我下载了javamail api源代码()并编辑了com.sun.mail.auth.Ntlm类,以使用JCIFS库支持()添加对NTLMv2缺少的支持 文件Ntlm.java中的第一个修改是在方法init0中,包括添加缺少的标志NTLMSSP\u congregate\u 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());
}
我发现修补库的simpest方法是编译类并更新库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
为了尽可能地启用调试,我在测试类的主方法中使用了以下代码:
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
在应用修补程序之前,由于我的Exchange服务器需要NTLMv2身份验证,测试在发送类型1消息后被卡住。
修补后,感应成功完成
另一种解决方案是通过##Exchange webservice#####又称EWS通过使用由Microsoft()发布和维护的##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,如本文的答案所示:
即:
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());
}
}
我试过了,效果很好。根据javamail常见问题解答,您应该使用\bob\bob(假设“bob”是NT用户名和邮件地址)。这就是您在遇到“一次性使用”错误时尝试的方法吗?如果您可以嗅探到与Exchange服务器的Mail.app连接,您可能会看到它是如何进行身份验证的(虽然您不知道如何生成令牌,但您可能会看到它是否在执行与JavaMail不同的操作)。@Chochos,我从未被要求为Mail.app输入NT用户名。我也尝试使用用户名,但得到了相同的错误。@Chochos,我尝试启动TCPMonitor在localhost:9143上侦听,并将请求转发到端口143上的服务器,但我得到的是:*好的server18.example.nl Cyrus IMAP4 v2.1.18-IPv6-Debian-2.1.18-5.1服务器准备好了吗?除了143之外,还有其他端口在使用吗?IMAP应该只有143个…嗨,Jon,没什么运气,结果是一样的。如果我在windows以外的其他设备上运行呢?我仍然可以对NTLM服务器进行身份验证吗?如果我们讨论的是NTLMv2,很可能不会。顺便说一句:我只记得当时我们采用的解决方案是使用Kerberos。大多数exchange支持Kerberos身份验证,该协议支持凭据委派,我认为如果您从非windows站点浏览,它也可以工作。问题是关于IMAP,没有SMTP身份验证
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());
}