Author |
Topic: How to add an active user account to Active Directory |
|
JNDI member offline |
|
posts: |
19 |
joined: |
05/18/2007 |
from: |
GA |
|
|
|
|
|
How to add an active user account to Active Directory |
Why does it matter?
Let's first look at the code below:
/**
* Sample JNDI example code to add a user account to SunOne
*/
import javax.naming.*;
import javax.naming.directory.*;
import java.util.Hashtable;
public class JNDI_Add_User {
public static void main(String[] args)
{
String ldapURL = "ldap://mySunOne.myCompany.com:389";
String bindDn = "cn=Directory Manager";
String bindPwd = "password";
// Set up the environment for creating the initial context
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, ldapURL);
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, bindDn);
env.put(Context.SECURITY_CREDENTIALS, bindPwd);
try {
// Create the initial context
DirContext ctx = new InitialDirContext(env);
// Attributes to represent the user
Attributes attrs = new BasicAttributes(true); // case-ignore
// objectClass
Attribute attr = new BasicAttribute("objectClass");
attr.add("top");
attr.add("person");
attr.add("organizationalPerson");
attr.add("inetOrgPerson");
attrs.put(attr);
// MUST attribute
attrs.put("cn", "John Smith");
// MAY attribute
attrs.put("givenName", "John");
attrs.put("sn", "Smith");
attrs.put("userPassword", "password");
// Create the user account
ctx.createSubcontext(
"cn=John Smith,ou=People,dc=example,dc=com",
attrs);
// close
ctx.close();
} catch (NamingException e) {
e.printStackTrace();
}
}
}
It's working perfectly for most LDAP compliant directory servers including openLDAP, openDJ and SunOne. The newly added user account can be used to provide authentication service.
Now run the same code (with necessary changes on objectclass and others) to add user into AD (Active Directory).
/**
* Sample JNDI example code to add a user account in Active Directory
*/
import javax.naming.*;
import javax.naming.directory.*;
import java.util.Hashtable;
public class JNDI_Add_User {
public static void main(String[] args)
{
String ldapURL = "ldap://myAD.myCompany.com:389";
String bindDn = "CN=Administrator,CN=Users,DC=myCompany,DC=com";
String bindPwd = "password";
// Set up the environment for creating the initial context
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, ldapURL);
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, bindDn);
env.put(Context.SECURITY_CREDENTIALS, bindPwd);
try {
// Create the initial context
DirContext ctx = new InitialDirContext(env);
// Attributes to represent the user
Attributes attrs = new BasicAttributes(true); // case-ignore
// objectClass
Attribute attr = new BasicAttribute("objectClass");
attr.add("top");
attr.add("person");
attr.add("organizationalPerson");
attr.add("user");
attrs.put(attr);
// MAY attribute
attrs.put("cn", "John Smith");
attrs.put("givenName", "John");
attrs.put("sn", "Smith");
attrs.put("userPassword", "password");
// Create the user account
ctx.createSubcontext(
"cn=John Smith,CN=Users,DC=myCompany,DC=com",
attrs);
// close
ctx.close();
} catch (NamingException e) {
e.printStackTrace();
}
}
}
The user account is added. But when this account is used for authentication, you will get the error similar like this: javax.naming.AuthenticationException: [LDAP: error code 49 - 80090308: LdapErr: DSID-0C0903A9, comment: AcceptSecurityContext error, data 52e, v1db1
The account is useless! Why?
|
|
|
|
|
|
|
JNDI member offline |
|
posts: |
19 |
joined: |
05/18/2007 |
from: |
GA |
|
|
|
|
|
Bumpy road to add user into Active Directory -- userAccountControl=default |
UserAccountControl is Active Directory's attribute that can control the behavior of user account. With this attribute, Active Directory can prevent unauthorized changes from messing up the user account. The value that is assigned to the attribute is cumulative with the most common flags as listed below:
2 -- ACCOUNTDISABLE
32 -- PASSWD_NOTREQD
512 -- NORMAL_ACCOUNT
For details, please check at: http://support.microsoft.com/kb/305144
The user account created in the above example bears the attribute userAccountControl with value of 546, which is default.
dn: cn=John Smith,CN=Users,DC=myCompany,DC=com
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: user
sAMAccountName: $N41000-S9ETGFD3FG6DS
userPassword:: DA9sjHg7y9UW8bf==
cn: John Smith
givenName: John
sn: Smith
userAccountControl: 546
Here, 546 = 512(NORMAL_ACCOUNT) + 32(PASSWD_NOTREQD) + 2(ACCOUNTDISABLE) which internally flags the account as: ACCOUNTDISABLE. That perfectly explains why the above newly added account is useless.
|
|
|
|
|
|
|
JNDI member offline |
|
posts: |
19 |
joined: |
05/18/2007 |
from: |
GA |
|
|
|
|
|
Bumpy road to add user into Active Directory -- userAccountControl=544 |
Let's force it to be enabled:
try {
// Create the initial context
DirContext ctx = new InitialDirContext(env);
// Attributes to represent the user
Attributes attrs = new BasicAttributes(true); // case-ignore
// objectClass
Attribute attr = new BasicAttribute("objectClass");
attr.add("top");
attr.add("person");
attr.add("organizationalPerson");
attr.add("user");
attrs.put(attr);
// MAY attribute
attrs.put("cn", "John Smith");
attrs.put("givenName", "John");
attrs.put("sn", "Smith");
attrs.put("userPassword", "password");
attrs.put("userAccountControl", "544");
// Create the user account
ctx.createSubcontext(
"cn=John Smith,CN=Users,DC=myCompany,DC=com",
attrs);
// close
ctx.close();
} catch (NamingException e) {
e.printStackTrace();
}
Here, 544 = 512(NORMAL_ACCOUNT) + 32(PASSWD_NOTREQD) which intentionally flags the account NOT as: ACCOUNTDISABLE. But when this account is used for authentication, you still get the same error as before: javax.naming.AuthenticationException: [LDAP: error code 49 - 80090308: LdapErr: DSID-0C0903A9, comment: AcceptSecurityContext error, data 52e, v1db1
Wait a minute, look at the flag 32(PASSWD_NOTREQD). Does that means what the user provided through userPassword was not even treated as password?
|
|
|
|
|
|
|
JNDI member offline |
|
posts: |
19 |
joined: |
05/18/2007 |
from: |
GA |
|
|
|
|
|
Bumpy road to add user into Active Directory -- userAccountControl=512 |
Ok, let's try one more step with userAccountControl being 512 - We want to create a normal active account with password:
try {
// Create the initial context
DirContext ctx = new InitialDirContext(env);
// Attributes to represent the user
Attributes attrs = new BasicAttributes(true); // case-ignore
// objectClass
Attribute attr = new BasicAttribute("objectClass");
attr.add("top");
attr.add("person");
attr.add("organizationalPerson");
attr.add("user");
attrs.put(attr);
// MAY attribute
attrs.put("cn", "John Smith");
attrs.put("givenName", "John");
attrs.put("sn", "Smith");
attrs.put("userPassword", "password");
attrs.put("userAccountControl", "512");
// Create the user account
ctx.createSubcontext(
"cn=John Smith,CN=Users,DC=myCompany,DC=com",
attrs);
// close
ctx.close();
} catch (NamingException e) {
e.printStackTrace();
}
Sorry, you are not allowed to do that. Here is the error message: javax.naming.OperationNotSupportedException: [LDAP: error code 53 - 0000052D: SvcErr: DSID-031A120C, problem 5003 (WILL_NOT_PERFORM), data 0
Hmmm?
|
|
|
|
|
|
|
JNDI member offline |
|
posts: |
19 |
joined: |
05/18/2007 |
from: |
GA |
|
|
|
|
|
Bumpy road to add user into Active Directory -- SSL/StartTLS |
Let's try it again with SSL a or StartTLS secure connection.
String ldapURL = "ldaps://myAD.myCompany.com:636";
String bindDn = "CN=Administrator,CN=Users,DC=myCompany,DC=com";
String bindPwd = "password";
Sorry, it doesn't help. Same error: javax.naming.OperationNotSupportedException: [LDAP: error code 53 - 0000052D: SvcErr: DSID-031A120C, problem 5003 (WILL_NOT_PERFORM), data 0
Ouch!?
|
|
|
|
|
|
|
JNDI member offline |
|
posts: |
19 |
joined: |
05/18/2007 |
from: |
GA |
|
|
|
|
|
Bumpy road to add user into Active Directory -- unicodePwd |
As shown above, Microsoft does not allow client application to directly manipulate attribute userPassword. Instead, Microsoft introduced a mysterious attribute unicodePwd which requires the password value be enclosed in double quotes and then each character (including the quotes) must be converted to its UTF16 unicode equivalent (because Windows conforms to UTF16). If you want to batch load users with LDIF, it must be further converted to Base64 encoding.
For example, for the password is Abcd1234, the LDIF representation should NOT be
Instead, it MUST be:
unicodePwd:: IgBBAGIAYwBkADEAMgAzADQAIgA=
Here is the whole chain of conversion:
Abcd1234
|
|
v
"Abcd1234"
|
|
v
0x22 00 41 00 62 00 63 00 64 00 31 00 32 00 33 00 34 00 22 00
|
|
v
IgBBAGIAYwBkADEAMgAzADQAIgA=
Whoa, that's really a myth!
|
|
|
|
|
|
|
JNDI member offline |
|
posts: |
19 |
joined: |
05/18/2007 |
from: |
GA |
|
|
|
|
|
Bumpy road to add user into Active Directory -- the End. |
Finally, if you want to create a normal active user account with password Abcd1234, here is the complete code:
/**
* Sample JNDI example code to add a user account in Active Directory
*/
import javax.naming.*;
import javax.naming.directory.*;
import java.util.Hashtable;
public class JNDI_Add_User {
public static void main(String[] args)
{
String ldapURL = "ldaps://myAD.myCompany.com:636"; // SSL
String bindDn = "CN=Administrator,CN=Users,DC=myCompany,DC=com";
String bindPwd = "password";
// Set up the environment for creating the initial context
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, ldapURL);
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, bindDn);
env.put(Context.SECURITY_CREDENTIALS, bindPwd);
try {
// Create the initial context
DirContext ctx = new InitialDirContext(env);
// Attributes to represent the user
Attributes attrs = new BasicAttributes(true); // case-ignore
// objectClass
Attribute attr = new BasicAttribute("objectClass");
attr.add("top");
attr.add("person");
attr.add("organizationalPerson");
attr.add("user");
attrs.put(attr);
// MUST attribute
attrs.put("unicodePwd", "\"Abcd1234\"".getBytes("UTF-16LE") );
attrs.put("userAccountControl", "512");
// MAY attribute
attrs.put("cn", "John Smith");
attrs.put("givenName", "John");
attrs.put("sn", "Smith");
// Create the user account
ctx.createSubcontext(
"cn=John Smith,CN=Users,DC=myCompany,DC=com",
attrs);
// close
ctx.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
Note: unicodePwd with the mysterious conversion is required; userAccountControl=512 is required, otherwise it will be defaulted to 546; SSL or StartTLS secure connection is required; There may be some password policy you have to follow, like minimum length of password.
|
|
|
|
|
|
|
asakhi member offline |
|
posts: |
|
joined: |
04/08/2014 |
from: |
Toronto, ON |
|
|
|
|
|
|
I know this is an old thread. I am doing something similar, but reseting user's password in AD. I am having trouble getting the unicoded password value. Can you please tell us how did you get the value of the password in unicode? i.e. the final value in this example (IgBBAGIAYwBkADEAMgAzADQAIgA=).
I used the following, but it appears that it returns memory location and not actual value.
quotedPassword.getBytes("UTF-16LE");
Help please??
Here is the whole chain of conversion: Abcd1234 | | v "Abcd1234" | | v 0x22 00 41 00 62 00 63 00 64 00 31 00 32 00 33 00 34 00 22 00 | | v IgBBAGIAYwBkADEAMgAzADQAIgA= |
|
|
|
|
|
|
asakhi member offline |
|
posts: |
|
joined: |
04/08/2014 |
from: |
Toronto, ON |
|
|
|
|
|
|
I know this is an old thread. I am doing something similar, but reseting user's password in AD. I am having trouble getting the unicoded password value. Can you please tell us how did you get the value of the password in unicode? i.e. the final value in this example (IgBBAGIAYwBkADEAMgAzADQAIgA=).
I used the following, but it appears that it returns memory location and not actual value.
quotedPassword.getBytes("UTF-16LE");
Help please??
Here is the whole chain of conversion: Abcd1234 | | v "Abcd1234" | | v 0x22 00 41 00 62 00 63 00 64 00 31 00 32 00 33 00 34 00 22 00 | | v IgBBAGIAYwBkADEAMgAzADQAIgA= |
|
|
|
|
|
|
asakhi member offline |
|
posts: |
|
joined: |
04/08/2014 |
from: |
Toronto, ON |
|
|
|
|
|
|
I know this is an old thread. I am doing something similar, but reseting user's password in AD. I am having trouble getting the unicoded password value. Can you please tell us how did you get the value of the password in unicode? i.e. the final value in this example (IgBBAGIAYwBkADEAMgAzADQAIgA=).
I used the following, but it appears that it returns memory location and not actual value.
quotedPassword.getBytes("UTF-16LE");
Help please??
Here is the whole chain of conversion: Abcd1234 | | v "Abcd1234" | | v 0x22 00 41 00 62 00 63 00 64 00 31 00 32 00 33 00 34 00 22 00 | | v IgBBAGIAYwBkADEAMgAzADQAIgA= |
|
|
|
|
|
|
asakhi member offline |
|
posts: |
|
joined: |
04/08/2014 |
from: |
Toronto, ON |
|
|
|
|
|
|