這編主要是描述 Java JNDI 連 Windows Active Directory 的教程.
包括認證, 新增用戶, 修改密碼 及 取得用戶資料.
開始教程:
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 *****************/连接来源:http://www.sawin.cn/doc/SP/Java/TheEdge202.htm
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 *****************/