更改用户账户密码,必须要使用ssl方式登录到AD。
网上大部分教程使用TrustStore的方式连接,
Hashtable env = newHashtable();
System.setProperty("javax.net.ssl.trustStore", KEYSTORE);
System.setProperty("javax.net.ssl.trustStorePassword", KEYSTORE_PWD);
env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.SECURITY_AUTHENTICATION,"simple");
env.put(Context.SECURITY_PRINCIPAL, ADMIN_NAME);
env.put(Context.SECURITY_CREDENTIALS, ADMIN_PASSWORD);
env.put(Context.SECURITY_PROTOCOL,"ssl");
env.put(Context.PROVIDER_URL, LDAP_SSL_URL);try{
ctx= new InitialLdapContext(env, null);//new InitialDirContext(HashEnv);//初始化上下文
} catch(AuthenticationException e) {
System.out.println("身份验证失败!"+e.toString());
e.printStackTrace();
}catch(CommunicationException e) {
System.out.println("AD域连接失败!"+e.toString());
e.printStackTrace();
}catch(Exception e) {
System.out.println("身份验证未知异常!"+e.toString());
e.printStackTrace();
}finally{returnctx;
}
最后ssl连接失败,报如下错误:javax.naming.CommunicationException: simple bind failed: xxxx:636 [Root exception is 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]。
也有部分教程提到了绕过ssl的方式,但是都只有部分代码,直到遇到了这段代码
重要备注:虽然该方式可以避免使用TrustStore认证的方式,但是ad域控服务器仍然需要添加信任证书,如下
且该证书对应的域名必须和AD域控所管理的域保持一致(也可能是AD域对应的证书服务器颁发的证书,我们的域控恰好是对应的证书服务器),域控处于dccn.com, 则证书不能使用例如abc.test.com,否则会报DNS异常如下:
javax.naming.CommunicationException: simple bind failed: cdt.xxx.cn:636 [Root exception is javax.net.ssl.SSLHandshakeException: java.security.cert.CertificateException: No subject alternative DNS name matching cdstest.huanghongbo.cn found.]
完整代码如下:
packageorg.util.ad;importjavax.naming.Context;importjavax.naming.NamingEnumeration;importjavax.naming.NamingException;import javax.naming.directory.*;importjavax.naming.ldap.Control;importjavax.naming.ldap.InitialLdapContext;importjavax.naming.ldap.LdapContext;importjavax.naming.ldap.SortControl;importjava.io.UnsupportedEncodingException;importjava.util.Calendar;importjava.util.Date;importjava.util.Hashtable;public classLdapSSLUtil {
//ldap用户登录的3种方式
//方式1. 域\account
private final static String adminName = "tylincn02\\tylinoap";
//方式2. account@域
// private final static String adminName = "tylinoap@tylincn.com";
//方式3. cn用户(网上最常见的方式可能会有重名的情况,不适用于用户查找,不推荐)
// private final static String adminName = "cn=tylinoap,OU=ITUSER,DC=tylincn,DC=com";
private final static String adminName = "xxx";private final static String adminPassword = "xxxx";private final String ldapURL = "LDAPS://xxx.xxx.xxx:636"; //注意,必须使用域名加636端口
private final String factory = "com.sun.jndi.ldap.LdapCtxFactory";private final String BASEN = "OU=xx,DC=xx,DC=xx";private LdapContext ctx = null;private final Control[] sortConnCtls = new SortControl[1];/*** 用户认证
*
*@paramuserName
*@parampassword*/
public voidldap_connect(String userName, String password) {
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY, factory);
env.put(Context.SECURITY_AUTHENTICATION,"simple");
env.put(Context.SECURITY_PRINCIPAL, userName);
env.put(Context.SECURITY_CREDENTIALS, password);
env.put(Context.PROVIDER_URL, ldapURL);
env.put(Context.SECURITY_PROTOCOL,"ssl");
env.put("java.naming.ldap.factory.socket", "org.util.ad.DummySSLSocketFactory");try{//初始化ldapcontext,方式1//ctx = new InitialLdapContext(env, null);//方式2
sortConnCtls[0] = new SortControl("sAMAccountName", Control.CRITICAL);
ctx= newInitialLdapContext(env, sortConnCtls);
System.out.println("认证成功");
}catch(Exception e) {
System.out.println("认证失败");
e.printStackTrace();
}
}//关闭连接
public voidclose_connect() {try{if (ctx != null) {
ctx.close();
}
}catch(NamingException e) {
e.printStackTrace();
}
}/*** 查找用户信息
*
*@paramcn
*@return
*/
publicAttributes getUser(String cn) {
Attributes attrs= null;
SearchControls contro= newSearchControls();
contro.setSearchScope(SearchControls.SUBTREE_SCOPE);try{//有的企业员工的dn不是有cn开头的,而是由uid开头的,这个因企业而异//使用cn,若存在重名用户,则返回的是最后一个员工,存在bug//NamingEnumeration en = ctx.search(BASEN, "cn=" + cn, contro);//使用sAMAccountName,避免重名,比如存在四个张伟
NamingEnumeration en = ctx.search(BASEN, "sAMAccountName=" +cn, contro);if (en == null || !en.hasMoreElements()) {
System.out.println("未找到该用户:" +cn);return null;
}while(en.hasMoreElements()) {
Object obj=en.nextElement();if (obj instanceofSearchResult) {
SearchResult si=(SearchResult) obj;
attrs=si.getAttributes();//attrs是用户的一些相关属性,一些很重要的属性
System.out.println(attrs);
}
}
}catch(NamingException e) {
System.out.println("查找用户异常。。。");
e.printStackTrace();
}returnattrs;
}/*** 获取用户的dn
*
*@paramcn
*@return
*/
publicString getUserDN(String cn) {
Attributes attrs=getUser(cn);//distinguishedname这个属性即是用户的dn,可以打印看看
String userDN = attrs.get("distinguishedname").toString().split(":")[1].trim();returnuserDN;
}//解锁账号和下次登录需要修改密码
public voidenableUser(String userName) {
String userDN=getUserDN(userName);
BasicAttributes attrsbu= newBasicAttributes();//这个是重点,下面有话有说
attrsbu.put("userAccountControl", "512");
attrsbu.put("pwdLastSet", "0");try{
ctx.modifyAttributes(userDN, DirContext.REPLACE_ATTRIBUTE, attrsbu);
System.out.println("解锁账号成功");
}catch(NamingException e) {
System.out.println("解锁账号失败");
e.printStackTrace();
}
}//重置密码
public void updateUserPassword(String cn, String newPassword) throwsUnsupportedEncodingException, NamingException {
ModificationItem[] mods= new ModificationItem[1];
String newQuotedPassword= "\"" + newPassword + "\"";byte[] newUnicodePassword = newQuotedPassword.getBytes("UTF-16LE");
mods[0] = newModificationItem(DirContext.REPLACE_ATTRIBUTE,new BasicAttribute("unicodePwd", newUnicodePassword));//修改密码
String userDN =getUserDN(cn);
ctx.modifyAttributes(userDN, mods);
}/*** AD账户时间戳转换
*@paramaccountExpiresL
*@return
*/
public static Date adExpiresToDate(longaccountExpiresL){
Calendar calendar=Calendar.getInstance();
calendar.clear();
calendar.set(1601, 0, 1, 0, 0);
accountExpiresL= accountExpiresL/ 10000 +calendar.getTime().getTime();return newDate(accountExpiresL);
}/*** 获取AD账户失效日期
*@paramaccount
*@return
*/
publicDate getAccountExpiresToDate(String account){
Attributes attrs=getUser(account);//distinguishedname这个属性即是用户的dn,可以打印看看
String accountexpires = attrs.get("accountexpires").toString().split(":")[1].trim();//return accountexpires;
System.out.println(accountexpires);
Long expiresLong=Long.parseLong(accountexpires);
Date expiresDate=adExpiresToDate(expiresLong);
System.out.println(expiresDate);returnexpiresDate;
}/*** 上次密码修改时间
*@paramaccount
*@return
*/
publicDate getPwdLastSetTime(String account){
Attributes attrs=getUser(account);//distinguishedname这个属性即是用户的dn,可以打印看看
String pwdlastset = attrs.get("pwdlastset").toString().split(":")[1].trim();//return accountexpires;
System.out.println(pwdlastset);
Long pwdlastsetLong=Long.parseLong(pwdlastset);
Date pwdLastSetDate=adExpiresToDate(pwdlastsetLong);
System.out.println(pwdLastSetDate);returnpwdLastSetDate;
}public static void main(String[] args)throwsException{
LdapSSLUtil ldapUtil= newLdapSSLUtil();
ldapUtil.ldap_connect(adminName, adminPassword);
String account= "xxx";//重置密码
ldapUtil.updateUserPassword(account,"xxxx");
ldapUtil.getUserDN(account);
ldapUtil.getAccountExpiresToDate(account);
ldapUtil.getPwdLastSetTime(account);
ldapUtil.close_connect();
}
}
关于AD用户
参考:
https://blog.csdn.net/xuxiaoqun0_0/article/details/82052218
https://blogs.msdn.microsoft.com/alextch/2012/05/15/how-to-set-active-directory-password-from-java-application/
https://www.iteye.com/blog/chnic-2065877
https://my.oschina.net/haison/blog/678354?p={{page}}
https://blog.csdn.net/laxsong/article/details/51344002
https://www.cnblogs.com/sunjiguang/p/9257585.html
https://www.bbsmax.com/A/q4zVZ0nGzK/
https://www.cnblogs.com/nidongde/p/5364622.html
https://blog.csdn.net/hc1017/article/details/81293323?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task
https://blog.csdn.net/qq_41207282/article/details/97133887#comments
https://blog.csdn.net/hct368/article/details/97247258
https://www.cnblogs.com/amoyzhu/p/9259264.html
https://blog.51cto.com/gaowenlong/1969586
https://blog.51cto.com/gaowenlong/1969585