2014年4月22日 星期二

JAVA 利用 JNDI 做 LDAP 登入認證

其實要用JNDI做AD認證很簡單,只需要下面這樣


Hashtable env = new Hashtable ();
env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "LDAP://<LDAP Host>:<port>");
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, email);
env.put(Context.SECURITY_CREDENTIALS, password);
new InitialLdapContext(env);


但這篇要寫的是針對LDAP認證,因為偉大的MIS不會設定LDAP使用自訂的認證機制
所以認證的帳號我只能使用DN,不能用Email...


好~進入正題

基本是在系統上,使用者登入絕對是打 Email,
絕對不可能要求使用者去記得那一長串又臭又長的 DN
所以程式要做的事就很明瞭了,就是將 Email 轉成 DN
其實也就是先到 LDAP 下用 Mail 這個屬性找到該名使用者
拿到 SearchResult 以後 getName


首先,還是要先 init ,連線到 LDAP
跟上面AD認證不一樣的是,
這次不用先給SECURITY_PRINCIPAL跟SECURITY_CREDENTIALS


       private String BASEDN = "dc=example,dc=com";
       private void initialData() throws NamingException {
Hashtable env = new Hashtable ();
env.put(Context.INITIAL_CONTEXT_FACTORY, FACTORY);
env.put(Context.PROVIDER_URL, "LDAP://<LDAP Host>:<port>/");
env.put(Context.SECURITY_AUTHENTICATION, "simple");

ldapContext = new InitialLdapContext(env, connCtls);
}



接著這篇最重要的 Email 轉成 DN


private String getUserDN(String email) {
String userDN = null;

// Create the search controls
SearchControls searchCtls = new SearchControls();

// Specify the search scope
searchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE);

// specify the LDAP search filter
// String searchFilter = "(&(objectClass=*))";
String searchFilter = "(&(ObjectClass=*)(mail=" + email + "))";

// Search for objects using the filter
NamingEnumeration answer;
try {
answer = ldapContext.search(BASEDN, searchFilter, searchCtls);

if (answer != null) {
// Loop through the search results
while (answer.hasMoreElements()) {
SearchResult sr = (SearchResult) answer.next();
userDN = "";
userDN += sr.getName();
userDN += "," + BASEDN;
}
}

} catch (Exception e) {
}

return userDN;
}



主要的登入程式,沒什麼好說明的
就是設定完帳密,重新連線


public String auth(String email, String password) {
String userDN = getUserDN(email);

if(userDN==null) return "USERNAME_NOT_EXSIT";
try {
ldapContext.addToEnvironment(Context.SECURITY_PRINCIPAL, userDN);
ldapContext.addToEnvironment(Context.SECURITY_CREDENTIALS, password);
ldapContext.reconnect(connCtls);
System.out.println(userDN + " is authenticated");
return "SUCCESS";
} catch (AuthenticationException e) {
return "AUTH_FALED";
} catch (NamingException e) {
return "AUTH_FALED";
}
}


2014年4月1日 星期二

JavaMail IMAP Exchange Server 問題

stackoverflow 這裡有一篇跟我遇到同樣的問題,解法也一樣
http://stackoverflow.com/questions/10442877/java-mail-to-exchange-server-no-login-methods-supported

需求描述:
簡單來說,我需要跟 MS  Exchange Server 測試 IMAP 連線,並做後續 Mail 處理


問題 1. 最初的連線設定與 Exception:
TestIMAP.java
Properties props = new Properties();
session = Session.getDefaultInstance(props, null);
session.setDebug(true);
store = session.getStore("imap");
store.connect("host","user","password");


Exception:
DEBUG: setDebug: JavaMail version 1.4.5
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 "myHost", port 143, isSSL false
* OK The Microsoft Exchange IMAP4 service is ready.
A0 CAPABILITY
* CAPABILITY IMAP4 IMAP4rev1 LOGINDISABLED STARTTLS UIDPLUS CHILDREN IDLE NAMESPACE LITERAL+
A0 OK CAPABILITY completed.
DEBUG: protocolConnect login, host=myHost, user=myUser, password=
javax.mail.MessagingException: No login methods supported!;


根據此文章的答案,他也是參考自這裡
原因:
看這行 * CAPABILITY IMAP4 IMAP4rev1 LOGINDISABLED
Exchange Server 已回報 登入禁止

解決方法:
1. 升級JavaMail至1.4.5
2. 加上這行 props.put("mail.imap.starttls.enable", "true");


這樣就正常了嗎?? 並沒有~~


問題 2.:
Exception:
以上省略,跟問題1相同
A1 STARTTLS
A1 OK Begin TLS negotiation now.
DEBUG IMAP: STARTTLS Exception: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
messaging exception
javax.mail.MessagingException: STARTTLS failure;



這個問題就要參考這裡
原因:
我懶的翻譯了,就直接把重點貼出來吧~~XDD
MS Exchange SSL connection is not established properly by Java Mail API

解決方法:
其實我也沒很懂這個解法以及為什麼要這樣寫
猜測是去繼承覆寫掉 SSLSocketFactory 的 Method
在建構子寫一個空的信任管理物件去建立TLS SSLSocketFactory

public class ExchangeSSLSocketFactory extends SSLSocketFactory {

private SSLSocketFactory sslSocketFactory;
private SocketFactory socketFactory;

public ExchangeSSLSocketFactory() {
try {
socketFactory = SocketFactory.getDefault();

SSLContext context = SSLContext.getInstance("TLS");
context.init(null, new TrustManager[] { new EmptyTrustManager() },
null);
sslSocketFactory = (SSLSocketFactory) context.getSocketFactory();
} catch (Exception e) {
throw new RuntimeException(e);
}
}

private final class EmptyTrustManager implements X509TrustManager {
public void checkClientTrusted(X509Certificate[] cert, String authType)
throws CertificateException {
}

public void checkServerTrusted(X509Certificate[] cert, String authType)
throws CertificateException {
}

public X509Certificate[] getAcceptedIssuers() {
return new java.security.cert.X509Certificate[0];
}
}

public static SocketFactory getDefault() {
return new ExchangeSSLSocketFactory();
}

@Override
public Socket createSocket(Socket socket, String s, int i, boolean flag)
throws IOException {
return sslSocketFactory.createSocket(socket, s, i, flag);
}

@Override
public Socket createSocket(InetAddress inaddr, int i, InetAddress inaddr1,
int j) throws IOException {
return socketFactory.createSocket(inaddr, i, inaddr1, j);
}

@Override
public Socket createSocket(InetAddress inaddr, int i) throws IOException {
return socketFactory.createSocket(inaddr, i);
}

@Override
public Socket createSocket(String s, int i, InetAddress inaddr, int j)
throws IOException {
return socketFactory.createSocket(s, i, inaddr, j);
}

@Override
public Socket createSocket(String s, int i) throws IOException {
return socketFactory.createSocket(s, i);
}

@Override
public Socket createSocket() throws IOException {
return socketFactory.createSocket();
}

@Override
public String[] getDefaultCipherSuites() {
return sslSocketFactory.getSupportedCipherSuites();
}

@Override
public String[] getSupportedCipherSuites() {
return sslSocketFactory.getSupportedCipherSuites();
}

}

加上這兩個 Properties
props.setProperty("ssl.SocketFactory.provider", "your.package.name.ExchangeSSLSocketFactory");
props.setProperty("mail.imap.socketFactory.class", "your.package.name.ExchangeSSLSocketFactory");



OK, 打完收工

其實中間還有嘗試並看很多其他的解法,也有試著使用SSL imaps
但都無法成功,老實講 IMAP 的確比 POP3 麻煩與複雜多了
難怪現在還是一堆人只寫 POP3

其他參考資料
http://alvinalexander.com/blog/post/java/i-need-get-all-of-my-email-addresses-out-of-imap-mailbox
https://javamail.java.net/nonav/docs/api/