ToB项目身份认证AD集成(三完):利用ldap.js实现与windows AD对接实现用户搜索、认证、密码修改等功能 - 以及针对中文转义问题的补丁方法

简介: 本文详细介绍了如何使用 `ldapjs` 库在 Node.js 中实现与 Windows AD 的交互,包括用户搜索、身份验证、密码修改和重置等功能。通过创建 `LdapService` 类,提供了与 AD 服务器通信的完整解决方案,同时解决了中文字段在 LDAP 操作中被转义的问题。

在前面的两篇文章中,我详细的介绍了使用ldap与window AD服务集成,实现ToB项目中的身份认证集成方案,包括技术方案介绍、环境配置:
ToB项目身份认证AD集成(一):基于目录的用户管理、LDAP和Active Directory简述
ToB项目身份认证AD集成(二):一分钟搞定window server 2003部署AD域服务并支持ssl加密

在本文中,我将详细介绍如何利用 ldapjs 库使之一个 Node.js 服务类 LdapService,该类实现了与 之前搭建的Windows AD 交互,包括用户搜索、身份验证、密码修改等功能。

也算是AD集成系列的完结吧,后续可能出其它客户端的对接,但目前工作核心在AI那块儿,大概率也不会继续了

一、实现方案和LdapService类概述

LdapService 类的核心是通过 LDAP(轻量级目录访问协议)与 AD 进行交互,提供用户搜索、认证、密码修改、重置等功能。下图是该类的基本结构,后续将一步步的介绍如何实现各个方法。

class LdapService {
   
  client: Promise<ldap.Client>;
  private config: MustProperty<LdapServiceConfig>;
  constructor(config: LdapServiceConfig) {
   
    this.config = {
   
      ...defaultConfig,
      ...config,
    };
    this.client = this.init();
  }
  async findUsers(
    filter = this.config.userSearchFilter,
    attributes: string[] = ["sAMAccountName", "userPrincipalName", "memberOf"]
  ) {
   

  }
  // 关闭连接
  async close() {
   
    (await this.client).destroy();
  }

  async findUser() {
   

  }
  // 修改用户密码的方法
  async changePassword(
    user: LdapUserSimInfo,
    newPassword: string,
    oldPassword: string
  ) {
   

  }
  // 用户认证的方法 - 检查密码是否正确
  async checkPassword(user: LdapUserSimInfo, password: string) {
   

  }
  /*重置密码 */
  async resetPassword(user: LdapUserSimInfo, resetPassword: string) {
   

  }
  private async init() {
   
    const conf = this.config;
    const client = ldap.createClient({
   
      url: conf.url,
      tlsOptions: {
   
        minVersion: "TLSv1.2",
        rejectUnauthorized: false,
      },
    });
    await promisify(client.bind).call(client, conf.adminDN, conf.adminPassword);
    return client; // 返回绑定后的客户端
  }
  private mergeSearchEntryObjectAttrs(entry: ldap.SearchEntryObject) {
   

  }
  private doSearch(client: ldap.Client, opts: ldap.SearchOptions) {
   

  }
  private encodePassword(password) {
   

  }
  private safeDn(dn: string) {
   

  }
}

二、中文字段的特殊patch

ldap.js对于数据的字段进行了escape操作,会导致中文输入被转化成\xxx的形式,无论是接收的数据还是发送的请求,这时候会导致cn包含中文会出现错。需要用如下方法进行patch,通过在出现问题的rdn上配置unescaped参数控制是否对字符串进行escape(如果不知道啥是escape,参见十六进制转义escape介绍

const oldString = ldap.RDN.prototype.toString;
ldap.RDN.prototype.toString = function () {
   
  return oldString.call(this, {
    unescaped: this.unescaped });
};

加了这个补丁后,就可以控制rdn的转义情况了。

三、用户搜索功能

findUsers() 方法用于在 AD 中搜索用户,返回用户的基本信息。

async findUsers(
  filter = this.config.userSearchFilter,
  attributes: string[] = ["sAMAccountName", "userPrincipalName", "memberOf"]
): Promise<LdapUserSimInfo[]> {
   
    await this.bindAsAdmin();
    const opts = {
   
      filter, 
      scope: "sub", 
      attributes: Array.from(new Set(["distinguishedName", "cn"].concat(attributes))),
    };
    const searchResult = await this.doSearch(await this.client, opts);
    return searchResult.map((user) => {
   
      return this.mergeSearchEntryObjectAttrs(user) as LdapUserSimInfo;
    });
}
  • filter 是用于搜索的 LDAP 过滤器,默认为查找所有用户的 (objectClass=user) 过滤器。
  • attributes 参数允许指定返回哪些用户属性,默认返回 sAMAccountNameuserPrincipalNamememberOf 等属性。
  • 该方法调用了 doSearch() 进行搜索,并通过 mergeSearchEntryObjectAttrs() 整理和转换 AD 返回的用户数据。

doSearch() 方法是实际进行 LDAP 搜索的地方:

private doSearch(client: ldap.Client, opts: ldap.SearchOptions) {
   
    return new Promise<ldap.SearchEntryObject[]>((resolve, reject) => {
   
      const entries = [] as ldap.SearchEntryObject[];
      client.search(this.config.userSearchBase, opts, (err, res) => {
   
        if (err) {
   
          return reject(err);
        }
        res.on("searchEntry", (entry) => {
   
          entries.push(entry.pojo);
        });
        res.on("end", (result) => {
   
          if (result?.status !== 0) {
   
            return reject(new Error(`Non-zero status from LDAP search: ${
     result?.status}`));
          }
          resolve(entries);
        });
        res.on("error", (err) => {
   
          reject(err);
        });
      });
    });
}
  • client.search()ldapjs 提供的一个方法,用于执行搜索操作。搜索结果通过事件 searchEntry 逐条返回,最终在 end 事件时完成。

四、用户认证功能

checkPassword() 方法用于用户身份验证,检查用户输入的密码是否正确。

async checkPassword(user: LdapUserSimInfo, password: string) {
   
    const userDN = user.objectName;
    const client = await this.client;
    await promisify(client.bind).call(client, userDN, password);
}
  • 通过 LDAP 的 bind() 方法,可以尝试使用用户的 DN 和密码进行绑定。如果绑定成功,表示密码正确;否则,会抛出错误,表示认证失败。

五、密码修改功能

changePassword() 方法允许用户修改自己的密码。

async changePassword(user: LdapUserSimInfo, newPassword: string, oldPassword: string) {
   
    await this.bindAsAdmin();
    const userDN = this.safeDn(user.objectName);
    const changes = [
      new ldap.Change({
   
        operation: "delete",
        modification: new ldap.Attribute({
   
          type: "unicodePwd",
          values: [this.encodePassword(oldPassword)],
        }),
      }),
      new ldap.Change({
   
        operation: "add",
        modification: new ldap.Attribute({
   
          type: "unicodePwd",
          values: [this.encodePassword(newPassword)],
        }),
      }),
    ];
    const client = await this.client;
    await promisify(client.modify).call(client, userDN, changes);
}
  • 在修改密码时,LDAP 需要先删除旧密码,再添加新密码。这里使用 ldap.Change 创建修改操作,通过 client.modify() 方法应用到 AD。

六、密码重置功能

resetPassword() 方法允许管理员重置用户的密码:

async resetPassword(user: LdapUserSimInfo, resetPassword: string) {
   
    await this.bindAsAdmin();
    const client = await this.client;
    const userDN = this.safeDn(user.objectName);
    const changes = new ldap.Change({
   
      operation: "replace",
      modification: new ldap.Attribute({
   
        type: "unicodePwd",
        values: [this.encodePassword(resetPassword)],
      }),
    });
    await promisify(client.modify).call(client, userDN, changes);
}
  • 与修改密码不同,重置密码直接使用 replace 操作,替换用户的现有密码。

七、结语

通过对 LdapService 类的逐步解析,相信你已经学会了如何利用 ldapjs 库与 Windows AD 进行交互。在实际使用中,还可以根据业务需求对这个类进行扩展,从而满足大规模企业系统中的用户管理需求。

另外这个中文的问题,暂时还只能是如此打补丁,期待社区修复可能不会那么及时

相关文章
|
2月前
|
安全 算法 Java
数据库信息/密码加盐加密 —— Java代码手写+集成两种方式,手把手教学!保证能用!
本文提供了在数据库中对密码等敏感信息进行加盐加密的详细教程,包括手写MD5加密算法和使用Spring Security的BCryptPasswordEncoder进行加密,并强调了使用BCryptPasswordEncoder时需要注意的Spring Security配置问题。
174 0
数据库信息/密码加盐加密 —— Java代码手写+集成两种方式,手把手教学!保证能用!
|
3月前
|
Linux Android开发 iOS开发
Windows平台RTSP|RTMP播放器如何实现实时录像功能
Windows平台RTSP、RTMP播放器实时录像接口设计,实际上,除了Windows平台,我们Linux、Android、iOS平台也是一样的设计,单纯的录像模块,如果做的全面,也不是一两个接口可以搞定的
|
2月前
|
SQL 数据库连接 数据库
管理系统中的Visual Studio与SQL集成技巧与方法
在现代软件开发和管理系统中,Visual Studio(VS)作为强大的集成开发环境(IDE),与SQL数据库的紧密集成是构建高效、可靠应用程序的关键
|
2月前
|
SQL 监控 数据库
管理系统VS SQL:高效集成的关键技巧与方法
在现代企业信息化建设中,管理系统(如ERP、CRM等)与SQL数据库之间的紧密集成是确保数据流动顺畅、业务逻辑高效执行的关键
|
3月前
|
前端开发 JavaScript 开发者
Express.js与前端框架的集成:React、Vue和Angular的示例与技巧
本文介绍了如何将简洁灵活的Node.js后端框架Express.js与三大流行前端框架——React、Vue及Angular进行集成,以提升开发效率与代码可维护性。文中提供了详细的示例代码和实用技巧,展示了如何利用Express.js处理路由和静态文件服务,同时在React、Vue和Angular中构建用户界面,帮助开发者快速掌握前后端分离的开发方法,实现高效、灵活的Web应用构建。
60 3
|
2月前
|
程序员 Windows
程序员必备文件搜索工具 Everything 带安装包!!! 比windows自带的文件搜索快几百倍!!! 超级好用的文件搜索工具,仅几兆,不占内存,打开即用
文章推荐了程序员必备的文件搜索工具Everything,并提供了安装包下载链接,强调其比Windows自带搜索快且占用内存少。
49 0
|
3月前
|
并行计算 关系型数据库 分布式数据库
朗坤智慧科技「LiEMS企业管理信息系统」通过PolarDB产品生态集成认证!
近日,朗坤智慧科技股份有限公司「LiEMS企业管理信息系统软件」通过PolarDB产品生态集成认证!
|
4月前
|
存储 开发者 C#
WPF与邮件发送:教你如何在Windows Presentation Foundation应用中无缝集成电子邮件功能——从界面设计到代码实现,全面解析邮件发送的每一个细节密武器!
【8月更文挑战第31天】本文探讨了如何在Windows Presentation Foundation(WPF)应用中集成电子邮件发送功能,详细介绍了从创建WPF项目到设计用户界面的全过程,并通过具体示例代码展示了如何使用`System.Net.Mail`命名空间中的`SmtpClient`和`MailMessage`类来实现邮件发送逻辑。文章还强调了安全性和错误处理的重要性,提供了实用的异常捕获代码片段,旨在帮助WPF开发者更好地掌握邮件发送技术,提升应用程序的功能性与用户体验。
71 0
|
4月前
|
API C# Shell
WPF与Windows Shell完美融合:深入解析文件系统操作技巧——从基本文件管理到高级Shell功能调用,全面掌握WPF中的文件处理艺术
【8月更文挑战第31天】Windows Presentation Foundation (WPF) 是 .NET Framework 的关键组件,用于构建 Windows 桌面应用程序。WPF 提供了丰富的功能来创建美观且功能强大的用户界面。本文通过问题解答的形式,探讨了如何在 WPF 应用中集成 Windows Shell 功能,并通过具体示例代码展示了文件系统的操作方法,包括列出目录下的所有文件、创建和删除文件、移动和复制文件以及打开文件夹或文件等。
89 0
|
4月前
|
C# Windows 监控
WPF应用跨界成长秘籍:深度揭秘如何与Windows服务完美交互,扩展功能无界限!
【8月更文挑战第31天】WPF(Windows Presentation Foundation)是 .NET 框架下的图形界面技术,具有丰富的界面设计和灵活的客户端功能。在某些场景下,WPF 应用需与 Windows 服务交互以实现后台任务处理、系统监控等功能。本文探讨了两者交互的方法,并通过示例代码展示了如何扩展 WPF 应用的功能。首先介绍了 Windows 服务的基础知识,然后阐述了创建 Windows 服务、设计通信接口及 WPF 客户端调用服务的具体步骤。通过合理的交互设计,WPF 应用可获得更强的后台处理能力和系统级操作权限,提升应用的整体性能。
120 0

热门文章

最新文章

相关产品

  • IoT设备身份认证