這編主要是描述 Java JNDI 連 Windows Active Directory 的教程.包括認證, 新增用戶, 修改密碼 及 取得用戶資料.
作者原文:http://blog.matrix.org.cn/joeyta/entry/20070302
開始教程:
1. 建立 IIS SSL
2. 將 CA Certificate 加入至 jre keystore 裡
3. JNDI 連 AD
1. 建立 IIS SSL:
Install Windows 2003 Server:
Install AD:
Start -> Run -> dcpromote
domain name : joeyta-DOT-local
NT domain name : joeytaserver
即 Fully Qualified Domain Name (FQDN) 為 joeytaserver.joeyta-DOT-local
先安裝 IIS , 再安裝 CA.
Install IIS:
Start -> Programs -> Administrative Tools -> Configure Your Server Wizard
->> Next -> Next -> Application server (IIS, ASP.NET) -> Next
進入 http://joeyserver.joeyta.local/postinfo.html 表示安裝成功.
Install CA:
Start -> Settings -> Control Panel -> Add or Remove Programs
->> Add/Remove Windows Components
選擇 Certificate Services -> Next
選擇 Enterprise root CA -> Next
Common name for this CA: testca -> Next
進入 http://joeyserver.joeyta.local/CertSrv 表示安裝成功.
Generating a Certificate Signing Request:
Start -> Programs -> Administrative Tools -> Internet Information Services (IIS) Manager
->> Internet Information Services -> (local computer) -> Web Sites
-> > 右鍵點選 Default Web Site -> Properties
選擇 "Directory Security" -> Server Certificate
->> Create a new certificate -> Prepare the request now, but send it later
一直按 Next , 需要注意的是 Common name 必須為 joeyserver.joeyta.local, 這是給使用者連 ssl 的 website.
最後產生 certificate request file , 預設為 c:\certreq.txt
Request a certificate on CA:
進入 http://joeyserver.joeyta.local/CertSrv
按 Request a certificate -> advanced certificate request
-> Submit a certificate request by using a base-64-encoded CMC or PKCS#10 file, or submit a renewal request by using a base-64-encoded PKCS#7 file
使用 notepad 打開 c:\certreq.txt , copy c:\certreq.txt 內容貼至 Saved Request:
Certificate Template 選擇 Web Server, 按 Submit
然後點選 Download certificate , 將 certnew.cer 儲存至 c:\certnew.cer
Installing a Certificate:
Start -> Programs -> Administrative Tools -> Internet Information Services (IIS) Manager
->> Internet Information Services -> (local computer) -> Web Sites
-> > 右鍵點選 Default Web Site -> Properties
選擇 "Directory Security" -> Server Certificate
->> Process the pending request and install the certificate -> Next
Path and file name: c:\certnew.cer -> Next
SSL port this web site should use: 443 -> Next -> Next -> Finish
2. 將 CA Certificate 加入至 jre keystore 裡:
進入 http://joeyserver.joeyta.local/CertSrv
點選 Download a CA certificate, certificate chain, or CRL
點選 Download CA certificate , 然後下載並改名為 c:\testca_cert.cer
然後執行 command:
c:\temp>keytool -import -alias testca_cert -file "/testca_cert.cer" -keystore "/jdk1.5.0_09/jre/lib/security/cacerts" -storepass "changeit"
出現 Trusted this certificate? 按 "y" 即新增成功.
3. JNDI 連 AD:
/***************************** LDAPFastBind.java *****************/ package test.ldap; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.Hashtable; import javax.naming.AuthenticationException; import javax.naming.Context; import javax.naming.NamingEnumeration; import javax.naming.NamingException; import javax.naming.directory.Attribute; import javax.naming.directory.Attributes; import javax.naming.directory.BasicAttribute; import javax.naming.directory.BasicAttributes; import javax.naming.directory.DirContext; import javax.naming.directory.ModificationItem; import javax.naming.directory.SearchControls; import javax.naming.directory.SearchResult; import javax.naming.ldap.Control; import javax.naming.ldap.InitialLdapContext; import javax.naming.ldap.LdapContext; import javax.naming.ldap.StartTlsRequest; import javax.naming.ldap.StartTlsResponse; class FastBindConnectionControl implements Control { public byte[] getEncodedValue() { return null; } public String getID() { return "1.2.840.113556.1.4.1781"; } public boolean isCritical() { return true; } } public class LDAPFastBind { public Hashtable env = null; public LdapContext ctx = null; public Control[] connCtls = null; public LDAPFastBind(String ldapurl) { env = new Hashtable(); env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); env.put(Context.SECURITY_AUTHENTICATION, "simple"); env.put(Context.PROVIDER_URL, ldapurl); env.put(Context.SECURITY_PROTOCOL,"ssl"); String keystore = "/jdk1.5.0_09/jre/lib/security/cacerts"; System.setProperty("javax.net.ssl.trustStore",keystore); connCtls = new Control[] { new FastBindConnectionControl() }; // first time we initialize the context, no credentials are supplied // therefore it is an anonymous bind. try { ctx = new InitialLdapContext(env, connCtls); } catch (NamingException e) { System.out.println("Naming exception " + e); } } public boolean Authenticate(String username, String password) { try { ctx.addToEnvironment(Context.SECURITY_PRINCIPAL, username); ctx.addToEnvironment(Context.SECURITY_CREDENTIALS, password); ctx.reconnect(connCtls); System.out.println(username + " is authenticated"); return true; } catch (AuthenticationException e) { System.out.println(username + " is not authenticated"); System.out.println(e); return false; } catch (NamingException e) { System.out.println(username + " is not authenticated"); System.out.println(e); return false; } } public void finito() { try { ctx.close(); System.out.println("Context is closed"); } catch (NamingException e) { System.out.println("Context close failure " + e); } } public void printUserAccountControl() { try { // 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=user)(CN=test))"; //String searchFilter = "(&(objectClass=group))"; String searchFilter = "(&(objectClass=user)(CN=peter lee))"; // Specify the Base for the search String searchBase = "DC=joeyta,DC=local"; // initialize counter to total the group members int totalResults = 0; // Specify the attributes to return String returnedAtts[] = { "givenName", "mail" }; searchCtls.setReturningAttributes(returnedAtts); // Search for objects using the filter NamingEnumeration answer = ctx.search(searchBase, searchFilter, searchCtls); // Loop through the search results while (answer.hasMoreElements()) { SearchResult sr = (SearchResult) answer.next(); System.out.println(">>>" + sr.getName()); // Print out the groups Attributes attrs = sr.getAttributes(); if (attrs != null) { try { for (NamingEnumeration ae = attrs.getAll(); ae .hasMore();) { Attribute attr = (Attribute) ae.next(); System.out.println("Attribute: " + attr.getID()); for (NamingEnumeration e = attr.getAll(); e .hasMore(); totalResults++) { System.out.println(" " + totalResults + ". " + e.next()); } } } catch (NamingException e) { System.err.println("Problem listing membership: " + e); } } } System.out.println("Total attrs: " + totalResults); } catch (NamingException e) { System.err.println("Problem searching directory: " + e); } } public boolean adminChangePassword(String sUserName, String sNewPassword){ try { //set password is a ldap modfy operation ModificationItem[] mods = new ModificationItem[1]; //Replace the "unicdodePwd" attribute with a new value //Password must be both Unicode and a quoted string String newQuotedPassword = "\"" + sNewPassword + "\""; byte[] newUnicodePassword = newQuotedPassword.getBytes("UTF-16LE"); mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, new BasicAttribute("unicodePwd", newUnicodePassword)); // Perform the update ctx.modifyAttributes(sUserName, mods); System.out.println("Reset Password for: " + sUserName); return true; } catch (NamingException e) { System.out.println("Problem resetting password: " + e); } catch (UnsupportedEncodingException e) { System.out.println("Problem encoding password: " + e); } return false; } public boolean userChangePassword(String sUserName, String sOldPassword, String sNewPassword){ try { //StartTlsResponse tls = (StartTlsResponse)ctx.extendedOperation(new StartTlsRequest()); //tls.negotiate(); //change password is a single ldap modify operation //that deletes the old password and adds the new password ModificationItem[] mods = new ModificationItem[2]; //Firstly delete the "unicdodePwd" attribute, using the old password //Then add the new password,Passwords must be both Unicode and a quoted string String oldQuotedPassword = "\"" + sOldPassword + "\""; byte[] oldUnicodePassword = oldQuotedPassword.getBytes("UTF-16LE"); String newQuotedPassword = "\"" + sNewPassword + "\""; byte[] newUnicodePassword = newQuotedPassword.getBytes("UTF-16LE"); mods[0] = new ModificationItem(DirContext.REMOVE_ATTRIBUTE, new BasicAttribute("unicodePwd", oldUnicodePassword)); mods[1] = new ModificationItem(DirContext.ADD_ATTRIBUTE, new BasicAttribute("unicodePwd", newUnicodePassword)); // Perform the update ctx.modifyAttributes(sUserName, mods); System.out.println("Changed Password for: " + sUserName); //tls.close(); return true; } catch (NamingException e) { System.err.println("Problem changing password: " + e); } catch (UnsupportedEncodingException e) { System.err.println("Problem encoding password: " + e); } catch ( Exception e){ System.err.println("Problem: " + e); } return false; } public boolean createNewUser(String sGroupName, String sUserName){ try { // Create attributes to be associated with the new user Attributes attrs = new BasicAttributes(true); //These are the mandatory attributes for a user object //Note that Win2K3 will automagically create a random //samAccountName if it is not present. (Win2K does not) attrs.put("objectClass","user"); attrs.put("sAMAccountName","AlanT"); attrs.put("cn","Alan Tang"); //These are some optional (but useful) attributes attrs.put("givenName","Alan"); attrs.put("sn","Tang"); attrs.put("displayName","Alan Tang"); attrs.put("description","Engineer"); attrs.put("userPrincipalName","alan-AT-joeyta.local"); attrs.put("mail","alang-AT-mail.joeyta-DOT-local"); attrs.put("telephoneNumber","123 456 789"); //some useful constants from lmaccess.h int UF_ACCOUNTDISABLE = 0x0002; int UF_PASSWD_NOTREQD = 0x0020; int UF_PASSWD_CANT_CHANGE = 0x0040; int UF_NORMAL_ACCOUNT = 0x0200; int UF_DONT_EXPIRE_PASSWD = 0x10000; int UF_PASSWORD_EXPIRED = 0x800000; //Note that you need to create the user object before you can //set the password. Therefore as the user is created with no //password, user AccountControl must be set to the following //otherwise the Win2K3 password filter will return error 53 //unwilling to perform. attrs.put("userAccountControl",Integer.toString(UF_NORMAL_ACCOUNT + UF_PASSWD_NOTREQD + UF_PASSWORD_EXPIRED+ UF_ACCOUNTDISABLE)); // Create the context Context result = ctx.createSubcontext(sUserName, attrs); System.out.println("Created disabled account for: " + sUserName); //now that we've created the user object, we can set the //password and change the userAccountControl //and because password can only be set using SSL/TLS //lets use StartTLS //StartTlsResponse tls = (StartTlsResponse)ctx.extendedOperation(new StartTlsRequest()); //tls.negotiate(); //set password is a ldap modfy operation //and we'll update the userAccountControl //enabling the acount and force the user to update ther password //the first time they login ModificationItem[] mods = new ModificationItem[2]; //Replace the "unicdodePwd" attribute with a new value //Password must be both Unicode and a quoted string String newQuotedPassword = "\"P-AT-ssw0rd\""; byte[] newUnicodePassword = newQuotedPassword.getBytes("UTF-16LE"); mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, new BasicAttribute("unicodePwd", newUnicodePassword)); mods[1] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, new BasicAttribute("userAccountControl",Integer.toString(UF_NORMAL_ACCOUNT + UF_PASSWORD_EXPIRED))); // Perform the update ctx.modifyAttributes(sUserName, mods); System.out.println("Set password & updated userccountControl"); //now add the user to a group. try { ModificationItem member[] = new ModificationItem[1]; member[0]= new ModificationItem(DirContext.ADD_ATTRIBUTE, new BasicAttribute("member", sUserName)); ctx.modifyAttributes(sGroupName,member); System.out.println("Added user to group: " + sGroupName); } catch (NamingException e) { System.err.println("Problem adding user to group: " + e); } //Could have put tls.close() prior to the group modification //but it seems to screw up the connection or context ? //tls.close(); System.out.println("Successfully created User: " + sUserName); return true; } catch (NamingException e) { System.err.println("Problem creating object: " + e); } catch (IOException e) { System.err.println("Problem creating object: " + e); } return false; } public boolean addUserToGroup(LdapContext ctx, String userDN, String groupDN) { try{ ModificationItem[] mods = new ModificationItem[1]; mods[0] = new ModificationItem(DirContext.ADD_ATTRIBUTE, new BasicAttribute("member", userDN)); ctx.modifyAttributes(groupDN, mods); System.out.println("Added user " + userDN + " to group " + groupDN); return true; } catch (NamingException ne){ System.err.println("Problem add user to group: " + ne); } return false; } public boolean removeUserFromGroup(LdapContext ctx, String userDN, String groupDN) { try{ ModificationItem[] mods = new ModificationItem[1]; mods[0] = new ModificationItem(DirContext.REMOVE_ATTRIBUTE, new BasicAttribute("member", userDN)); ctx.modifyAttributes(groupDN, mods); System.out.println("Remove user " + userDN + " from group " + groupDN); return true; } catch (NamingException ne){ System.err.println("Problem remove user from group: " + ne); } return false; } } /***************************** LDAPFastBind.java *****************/
/***************************** LDAPClient.java *****************/ package test.ldap; class LDAPClient { public static void main(String[] args) { // Could also use ldaps over port 636 to protect the communication to // the // Active Directory domain controller. Would also need to add // env.put(Context.SECURITY_PROTOCOL,"ssl") to the "server" code //String ldapurl = "ldap://joeyserver.joeyta.local:389"; String ldapurl = "ldap://joeyserver.joeyta.local:636"; LDAPFastBind ctx = new LDAPFastBind(ldapurl); String sAdminUserName = "CN=Administrator,CN=Users,DC=joeyta,DC=local"; String sAdminPassword = "I@mRoot"; // String sUserName = "CN=peter lee,CN=Users,DC=joeyta,DC=local"; String sUserName = "joeyta\\peter"; // String sUserName = "peter@joeyta.local"; String sOldPassword = "P@ssw0rd"; String sNewPassword = "P@$$w0rd"; String sNewUserName = "CN=Alan Tang,CN=Users,DC=joyeta,DC=local"; String sNewGroupName = "CN=test,CN=Users,DC=joeyta,DC=local"; boolean IsAuthenticated = ctx.Authenticate(sAdminUserName, sAdminPassword); // boolean IsAuthenticated = ctx.Authenticate(sUserName, sOldPassword); ctx.printUserAccountControl(); ctx.createNewUser(sNewGroupName, sNewUserName); //boolean IsAdminSuccessChangePWD = ctx.adminChangePassword(sUserName,sNewPassword); //boolean IsUserSuccessChangePWD = ctx.userChangePassword(sUserName,sOldPassword,sNewPassword); ctx.finito(); } } /***************************** LDAPClient.java *****************/
參考資料:
http://www.javaworld.com.tw/jute/post/view?bid=7&id=164710&sty=1&tpg=1&age=0
http://forum.java.sun.com/forum.jspa?forumID=51&start=0