Windows域 - Java实现用户增删改查,修改用户密码
前言:
由于项目需要对接windows域,进行对 windows 域用户的管理和密码的初始化和修改的功能,本文也是针对各大博客的汇总,而不是我的原创,只是在尝试了很多博主的博客后,总结出来的一套可以使用的代码,本文中就不做太多的名词解释工作,直接了当的面对疾风
对于修改密码而言,我查看的博客分两类的描述,一种是修改属性 userPassword
例如:ldap使用java修改密码,第二种则是修改 unicodePwd
属性,根据官网地址描述可以确定是 unicodePwd
属性,官网地址我放到文末了
准备工作
- 一台 windows server 的服务器
- 一台普通 Windows PC
第一步,搭建 windows server 环境
略
第二步,获取 SSL 证书,并加入客户端
参考文章 AD域证书申请,导入Java密钥库,实现ldap修改AD用户密码
第三步,撸代码
<dependency><groupId>com.unboundid</groupId><artifactId>unboundid-ldapsdk</artifactId><version>6.0.8</version>
</dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId>
</dependency>
<dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.25</version>
</dependency>
- 信任自签证书
import javax.net.SocketFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;/*** 自定义的SSL工厂里面加载自己实现X509TrustManager,信任自签证书* @author zyred*/
public class CustomSslSocketFactory extends SSLSocketFactory {private SSLSocketFactory socketFactory;public CustomSslSocketFactory() {try {SSLContext ctx = SSLContext.getInstance("TLS");ctx.init(null, new TrustManager[]{new DummyTrustmanager()}, new SecureRandom());socketFactory = ctx.getSocketFactory();} catch (Exception ex) {ex.printStackTrace(System.err);}}public static SocketFactory getDefault() {return new CustomSslSocketFactory();}@Overridepublic String[] getDefaultCipherSuites() {return socketFactory.getDefaultCipherSuites();}@Overridepublic String[] getSupportedCipherSuites() {return socketFactory.getSupportedCipherSuites();}@Overridepublic Socket createSocket(Socket socket, String string, int num, boolean bool) throws IOException {return socketFactory.createSocket(socket, string, num, bool);}@Overridepublic Socket createSocket(String string, int num) throws IOException, UnknownHostException {return socketFactory.createSocket(string, num);}@Overridepublic Socket createSocket(String string, int num, InetAddress netAdd, int i) throws IOException, UnknownHostException {return socketFactory.createSocket(string, num, netAdd, i);}@Overridepublic Socket createSocket(InetAddress netAdd, int num) throws IOException {return socketFactory.createSocket(netAdd, num);}@Overridepublic Socket createSocket(InetAddress netAdd1, int num, InetAddress netAdd2, int i) throws IOException {return socketFactory.createSocket(netAdd1, num, netAdd2, i);}public static class DummyTrustmanager implements X509TrustManager {@Overridepublic void checkClientTrusted(X509Certificate[] cert, String string) throws CertificateException {}@Overridepublic void checkServerTrusted(X509Certificate[] cert, String string) throws CertificateException {}@Overridepublic X509Certificate[] getAcceptedIssuers() {return new java.security.cert.X509Certificate[0];}}
}
- 对用户的增删改查实现
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.*;
import javax.naming.ldap.InitialLdapContext;
import java.nio.charset.StandardCharsets;
import java.util.Hashtable;/*** <p>* </p>** @author zyred* @since v 0.0.1*/
@Slf4j
public class LdapUserUtil {/** 普通链接 **/private final InitialLdapContext ldapCtx;/** SSL链接 **/private final InitialLdapContext ldapsCtx;private static final String LDAP_URL = "ldap://10.20.1.11:389/";private static final String LDAP_S_URL = "ldaps://10.20.1.11:636/";private static final String ADMIN_NAME = "administrator@test.com";private static final String ADMIN_PWD = "rootroot";private LdapUserUtil () {this.ldapCtx = initCtx(false);this.ldapsCtx = initCtx(true);}/*** 支持普通客户端和 SSL 客户端初始化* @return 连接*/public InitialLdapContext initCtx(boolean ssl) {Hashtable<String, String> env = new Hashtable<>();env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");env.put(Context.PROVIDER_URL, ssl ? LDAP_S_URL : LDAP_URL);env.put(Context.SECURITY_AUTHENTICATION, "simple");env.put(Context.SECURITY_PRINCIPAL, ADMIN_NAME);env.put(Context.SECURITY_CREDENTIALS, ADMIN_PWD);if (ssl) {env.put(Context.SECURITY_PROTOCOL, "ssl");env.put("java.naming.ldap.factory.socket", CustomSslSocketFactory.class.getName());}try {return new InitialLdapContext(env, null);} catch (NamingException ex) {log.error("[{}连接异常] 异常:{}", ssl ? "SSL" : "普通", ex.getMessage(), ex);return null;}}/*** 添加用户** @param accountName 用户名:注意这里是用户的英文名,例如:zhangsan* @param displayName 用户名,也是显示在AD上的名字,例如:张三*/public void addUser(String accountName, String displayName, String baseDn) {try {Attributes attrs = new BasicAttributes(true);attrs.put("objectClass", "user");attrs.put("samAccountName", accountName);attrs.put("displayName", displayName);attrs.put("userPrincipalName", accountName + "@test.com");this.ldapCtx.createSubcontext("CN=" + accountName + "," + baseDn, attrs);} catch (Exception e) {log.error("[新增用户失败] 异常:{}", e.getMessage(), e);}}/*** 删除用户** @param dn dn 是完整的用户路径* 例如:CN=zhangsan,DC=test,DC=com* 有部门的情况:CN=zhangsan,OU=IT,DC=test,DC=com*/public void deleteUser(String dn) {try {this.ldapCtx.destroySubcontext(dn);} catch (Exception e) {log.error("[删除用户失败] 异常:{}", e.getMessage(), e);}}/*** 根据用户账户查询用户信息* 注意:这里一定不能用 张三 查询,而是用张三的账号:zhangsan 查询** @param accountName zhangsan* @param baseDn 域:"DC=test,DC=com"* @return 用户名*/private String queryByUserAccountName(String accountName, String baseDn) {SearchControls constraints = new SearchControls();constraints.setSearchScope(SearchControls.SUBTREE_SCOPE);constraints.setReturningObjFlag(true);String searchFilter = "(samAccountName=" + accountName + ")";try {NamingEnumeration<SearchResult> answer = this.ldapCtx.search(baseDn, searchFilter, constraints);while (answer.hasMoreElements()) {SearchResult obj = answer.nextElement();if (null != obj) {return obj.getName();}}log.info("[查询失败] 未找到用户:{}", accountName);} catch (Exception ex) {log.error("[查询用户失败] 异常:{}", ex.getMessage(), ex);}return null;}/*** 修改用户的密码** @param accountName 账户,如:zhangsan* @param newPwd 新密码:新密码有限制,如果需要关闭限制,则参考:{@see https://blog.csdn.net/techchan/article/details/5440775}* @param baseDn 域地址,如:DC=test,DC=com*/public void alertUserPassword(String accountName, String newPwd, String baseDn) {try {String username = this.queryByUserAccountName(accountName, baseDn);if (StrUtil.isNotBlank(username)) {String userDn = username + "," + baseDn;// 注意,这里必须要给新密码加上双引号,否则会报错String pwd = "\"" + newPwd + "\"";byte[] pwdBytes;pwdBytes = pwd.getBytes(StandardCharsets.UTF_16LE);ModificationItem[] mods = new ModificationItem[1];mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE,new BasicAttribute("unicodePwd", pwdBytes));this.ldapsCtx.modifyAttributes(LDAP_S_URL + userDn, mods);log.info("[修改成功] 账户:{}, 新密码:{}", accountName, newPwd);return;}log.warn("[用户找不到] 账户:{}", accountName);} catch (NamingException e) {log.error("[修改密码失败] 原因:{}", e.getMessage(), e);}}public static void main(String[] args) {// 这句代码是为了绕过SSL证书System.setProperty("com.sun.jndi.ldap.object.disableEndpointIdentification", "true");LdapUserUtil util = new LdapUserUtil();// 新增用户,注意:OU=生产部 必须要先去服务器上创建了才能使用,这里不提供创建util.addUser("xiaohong", "小红", "OU=生产部,DC=test,DC=com");// 查询用户String user = util.queryByUserAccountName("xiaohong", "DC=test,DC=com");System.out.println(user);// 删除用户, 字符串是需要加上这个用户的部门完整的 dn 信息util.deleteUser("CN=xiaohong,OU=生产部,DC=test,DC=com");// 修改用户的密码util.alertUserPassword("xiaohong", "123456", "OU=生产部,DC=test,DC=com");}
}
注意点:
- 如下这段代码的目的是为了绕开 SSL 检查,需要在启动阶段放入到环境变量中
如果出现 javax.naming.CommunicationException: simple bind failed: xxxxxtest.com.local:636
这种报错的时候,就需要加上
System.setProperty("com.sun.jndi.ldap.object.disableEndpointIdentification", "true");
- 密码不够复杂会报错,Windows server 关闭
密码策略
参考如下文章,常见的错误码也可以在这篇文章内找到
https://blog.csdn.net/techchan/article/details/5440775
- 新增用户的时候不能直接给用户设置密码,必须要新增成功后去修改用户密码,这句话是官网描述的
https://learn.microsoft.com/zh-cn/troubleshoot/windows-server/active-directory/change-windows-active-directory-user-password
- 对于 记一次java实现修改windows AD域的密码 这篇文章的代码,我做了一点改动
// 这段代码我没有传递 userDn 也能创建链接并修改成功,如果遇到无法连接的情况,修改 ssl 连接的完整地址即可
InitLDAPContext.sslInit(userDN);
最后验证结果
在我的验证结果中,windows11 家庭版是无法加入到域中的,只能专业版才行,其他的系统版本,我没有试过
这里我只说思路,但是我确确实实是验证成功的
-
进入设置 -> 系统 -> 系统信息,点击
域或工作组
-
点击
网络ID(N)...
,选择如下:然后下一步,下一步,下一步,连着三个
-
加入成功后,注销当前登录的用户
-
退出后用新用户和新密码登录即可
参考
- 记一次java实现修改windows AD域的密码
- AD域证书申请,导入Java密钥库,实现ldap修改AD用户密码
- 官网:通过 LDAP 更改 Windows Active Directory 和 LDS 用户密码