当前位置: 首页 > news >正文

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)...,选择如下:然后下一步,下一步,下一步,连着三个
    加入域j加入域

  • 加入成功后,注销当前登录的用户
    注销用户

  • 退出后用新用户和新密码登录即可

参考

  • 记一次java实现修改windows AD域的密码
  • AD域证书申请,导入Java密钥库,实现ldap修改AD用户密码
  • 官网:通过 LDAP 更改 Windows Active Directory 和 LDS 用户密码

http://www.mrgr.cn/news/80393.html

相关文章:

  • 【系统移植】交叉编译工具链常用工具
  • ElasticSearch 简介
  • qt-C++笔记之父类窗口、父类控件、对象树的关系
  • 【经验分享】搭建本地训练环境知识点及方法
  • 从〇开始深度学习(番外)——下载包
  • 家校通小程序实战教程09搭建部门管理APIs
  • 51c自动驾驶~合集41
  • DES笔记整理
  • D3 基础1
  • vue中slot插槽的使用(默认插槽,具名插槽,作用域插槽)
  • QT c++ 测控系统 一套报警规则(上)
  • 【代码随想录day59】【C++复健】 47. 参加科学大会(dijkstra(堆优化版)精讲 );94. 城市间货物运输 I
  • C语言(构造类型)
  • OpenIPC开源FPV之Adaptive-Link地面站代码解析
  • 锂电池SOH预测 | 基于BiGRU双向门控循环单元的锂电池SOH预测,附锂电池最新文章汇集
  • PHP8.4下webman直接使用topthink/think-orm
  • wazuh-modules-sca-scan
  • 【Qt】Qt+Visual Studio 2022环境开发
  • Guava 提供了集合操作 `List`、`Set` 和 `Map` 三个工具类
  • <数据集>输电线塔杂物识别数据集<目标检测>
  • uniapp滚动消息列表
  • OpenCV函数及其应用
  • dev类似于excel的数据编辑
  • Next.js流量教程:核心 Web Vitals的改善
  • ARM Cortex-A7 MPCore 架构
  • XML基础学习