更改用戶賬戶密碼,必須要使用ssl方式登錄到AD。
網上大部分教程使用TrustStore的方式連接,
Hashtable env = new Hashtable(); 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{ return ctx; }
最后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的方式,但是都只有部分代碼,直到遇到了這段代碼
原地址:JAVA修改AD域密碼_免證書
下載地址:JNDI免證書推送AD域密碼.zip
重要備注:雖然該方式可以避免使用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.]
完整代碼如下:
package org.util.ad; import javax.naming.Context; import javax.naming.NamingEnumeration; import javax.naming.NamingException; import javax.naming.directory.*; import javax.naming.ldap.Control; import javax.naming.ldap.InitialLdapContext; import javax.naming.ldap.LdapContext; import javax.naming.ldap.SortControl; import java.io.UnsupportedEncodingException; import java.util.Calendar; import java.util.Date; import java.util.Hashtable; public class LdapSSLUtil {
//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]; /** * 用戶認證 * * @param userName * @param password */ public void ldap_connect(String userName, String password) { Hashtable<String, Object> env = new Hashtable<String, Object>(); 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 = new InitialLdapContext(env, sortConnCtls); System.out.println("認證成功"); } catch (Exception e) { System.out.println("認證失敗"); e.printStackTrace(); } } //關閉連接 public void close_connect() { try { if (ctx != null) { ctx.close(); } } catch (NamingException e) { e.printStackTrace(); } } /** * 查找用戶信息 * * @param cn * @return */ public Attributes getUser(String cn) { Attributes attrs = null; SearchControls contro = new SearchControls(); contro.setSearchScope(SearchControls.SUBTREE_SCOPE); try { //有的企業員工的dn不是有cn開頭的,而是由uid開頭的,這個因企業而異 //使用cn,若存在重名用戶,則返回的是最后一個員工,存在bug // NamingEnumeration<SearchResult> en = ctx.search(BASEN, "cn=" + cn, contro); //使用sAMAccountName,避免重名,比如存在四個張偉 NamingEnumeration<SearchResult> 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 instanceof SearchResult) { SearchResult si = (SearchResult) obj; attrs = si.getAttributes(); //attrs是用戶的一些相關屬性,一些很重要的屬性 System.out.println(attrs); } } } catch (NamingException e) { System.out.println("查找用戶異常。。。"); e.printStackTrace(); } return attrs; } /** * 獲取用戶的dn * * @param cn * @return */ public String getUserDN(String cn) { Attributes attrs = getUser(cn); //distinguishedname這個屬性即是用戶的dn,可以打印看看 String userDN = attrs.get("distinguishedname").toString().split(":")[1].trim(); return userDN; } //解鎖賬號和下次登錄需要修改密碼 public void enableUser(String userName) { String userDN = getUserDN(userName); BasicAttributes attrsbu = new BasicAttributes(); //這個是重點,下面有話有說 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) throws UnsupportedEncodingException, NamingException { ModificationItem[] mods = new ModificationItem[1]; String newQuotedPassword = "\"" + newPassword + "\""; byte[] newUnicodePassword = newQuotedPassword.getBytes("UTF-16LE"); mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, new BasicAttribute("unicodePwd", newUnicodePassword)); // 修改密碼 String userDN = getUserDN(cn); ctx.modifyAttributes(userDN, mods); } /** * AD賬戶時間戳轉換 * @param accountExpiresL * @return */ public static Date adExpiresToDate(long accountExpiresL){ Calendar calendar = Calendar.getInstance(); calendar.clear(); calendar.set(1601, 0, 1, 0, 0); accountExpiresL = accountExpiresL/ 10000 + calendar.getTime().getTime(); return new Date(accountExpiresL); } /** * 獲取AD賬戶失效日期 * @param account * @return */ public Date 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); return expiresDate; } /** * 上次密碼修改時間 * @param account * @return */ public Date 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); return pwdLastSetDate; } public static void main(String[] args)throws Exception{ LdapSSLUtil ldapUtil = new LdapSSLUtil(); 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