dotNET Core 中怎樣操作 AD?

做企業應用開發難免會跟 AD 打交道,在之前的 dotNET FrameWork 時代,通常使用 System.DirectoryServices 的相關類來操作 AD ,在 dotNET Core 中沒有這個命名空間,在張善友大佬的推薦下,知道了 Novell.Directory.Ldap。

操作 AD,通常有兩種常見的場景:

  • 將第三方數據源數據(人事系統)同步到 AD 中
  • 將 AD 數據同步到自己的數據庫中

本文將介紹在 dotNET Core 中使用 Novell.Directory.Ldap 將 AD 數據同步到數據庫的操作。

環境

  • dotNET Core:2.1
  • Novell.Directory.Ldap.NETStandard2_0:3.1.0

安裝 Novell.Directory.Ldap 包

在 VS2019 中添加 NuGet 包引用,如下圖:

安裝完成後,在類中添加using Novell.Directory.Ldap;引用便可使用相關的 API 方法了。

同步思路

1、連接 AD
2、遍歷所有需要同步的根 OU
3、遞歸的方式進行部門和人員的同步操作

基本操作

同步方法

public bool Sync()
{
    ADConnect();
    if (_connection == null)
    {
        throw new Exception("AD連接錯誤,請確認AD相關信息配置正確!");
    }
    
    bool result = true;
    List<LdapEntry> entryList = this.GetRootEntries(_adPaths, _adHost);
    _org = new Org();
    _user = new User();
    Org rootOrg = _org.GetRootOrg();
    foreach (LdapEntry entry in entryList)
    {
        SyncDirectoryEntry(entry, rootOrg, entry);
    }

    return result;
}

連接 AD

public bool ADConnect()
{
    _adHost = "192.168.16.160";
    string adAdminUserName = "administrator";
    string adAdminPassword = "123456";
    _adPaths =new string[] { "OU=oec2003,DC=COM,DC=cn" };

    if ((string.IsNullOrEmpty(_adHost) || string.IsNullOrEmpty(adAdminUserName)) ||
        string.IsNullOrEmpty(adAdminPassword))
    {
        return false;
    }
    try
    {
        _connection = new LdapConnection();
        _connection.Connect(_adHost, LdapConnection.DEFAULT_PORT);
        _connection.Bind(adAdminUserName, adAdminPassword);
    }
    catch
    {
        return false;
    }

    return true;
}

遞歸操作

private void SyncDirectoryEntry(LdapEntry rootEntry, Org parentOrg, LdapEntry currentEntry)
{
    List<LdapEntry> entryList = currentEntry.Children(_connection);
    foreach (LdapEntry entry in entryList)
    {
        if (entry.IsOrganizationalUnit())
        {
            Org org = this.SyncOrgFromEntry(rootEntry, parentOrg, entry);
            this.SyncDirectoryEntry(rootEntry, org, entry);
        }
        else if (entry.IsUser())
        {
            this.SyncUserFromEntry(rootEntry, parentOrg, entry);
        }
    }
}

同步部門

private Org SyncOrgFromEntry(LdapEntry rootEntry, Org parentOrg, LdapEntry entry)
{
    string orgId = entry.Guid().ToLower();
    Org org = this._org.GetOrgById(orgId) as Org;
    if (org != null)
    {
        if (entry.ContainsAttr("ou"))
        {
            org.Name = entry.getAttribute("ou").StringValue + string.Empty;
        }
        //設置其他屬性的值
        _org.UpdateOrg(org);
        return org;
    }
    org = new Org
    {
        Id = orgId,
        ParentId = parentOrg.Id,
    };

    //設置其他屬性的值
    this._org.AddOrg(org);
    return org;
}

同步用戶

private User SyncUserFromEntry(LdapEntry rootEntry, Org parentOrg, LdapEntry entry)
{
    string userId = entry.Guid().ToLower();
    User user = this._user.GetUserById(userId);
    if (user != null)
    {
        user.ParentId = parentOrg.Id;
        //設置其他屬性的值
        this._user.UpdateUser(user);
          
        return user;
    }
    user = new User
    {
        Id = userId,
        ParentId = parentOrg.Id
    };
    //設置其他屬性的值
    this._user.AddUser(user);
    return user;
}

輔助方法

爲了方便代碼的編寫和複用,將一些操作提取到了擴展方法中。

獲取 Entry 的 GUID

public static string Guid(this LdapEntry entry)
{
    var bytes = (byte[])(entry.getAttribute("objectGUID").ByteValue as object);
    var guid = new Guid(bytes);
    return guid.ToString();
}

獲取 Entry 的 子級

public static List<LdapEntry> Children(this LdapEntry entry, LdapConnection connection)
{
    //string filter = "(&(objectclass=user))";
    List<LdapEntry> entryList = new List<LdapEntry>();
    LdapSearchResults lsc = connection.Search(entry.DN, LdapConnection.SCOPE_ONE, "objectClass=*", null, false);
    if (lsc == null) return entryList;

    while (lsc.HasMore())
    {
        LdapEntry nextEntry = null;
        try
        {
            nextEntry = lsc.Next();

            if (nextEntry.IsUser() || nextEntry.IsOrganizationalUnit())
            {
                entryList.Add(nextEntry);
            }
        }
        catch (LdapException e)
        {
            continue;
        }
    }
    return entryList;
}

判斷 Entry 是否爲用戶

public static bool IsUser(this LdapEntry entry)
{
    return entry.ObjectClass().Contains("user");
}

判斷 Entry 是否爲部門

public static bool IsOrganizationalUnit(this LdapEntry entry)
{
    return entry.ObjectClass().Contains("organizationalunit");
}

獲取 Entry 的修改時間

public static DateTime WhenChanged(this LdapEntry entry)
{
    string value = entry.getAttribute("whenChanged").StringValue;
    if (value.Split('.').Length > 1)
    {
        value = value.Split('.')[0];
    }
    DateTime whenChanged = DateTime.ParseExact(value, "yyyyMMddHHmmss", System.Globalization.CultureInfo.CurrentCulture);
    return whenChanged;
}

判斷 Entry 中屬性是否存在

public static bool ContainsAttr(this LdapEntry entry, string attrName)
{
    LdapAttribute ldapAttribute = new LdapAttribute(attrName);
    return entry.getAttributeSet().Contains(ldapAttribute);
}

根據名稱獲取 Entry 中的屬性值

public static string AttrStringValue(this LdapEntry entry, string attrName)
{
    if (!entry.ContainsAttr(attrName))
    {
        return string.Empty;
    }
    return entry.getAttribute(attrName).StringValue;
}

總結

文中沒有做更多文字性的介紹,可以從下面鏈接中下載代碼進行調試就很容易理解了。

參考

示例代碼:https://github.com/oec2003/StudySamples/tree/master/DotNetCoreAdDemo/DotNetCoreAdDemo
官方文檔:https://www.novell.com/documentation/developer/ldapcsharp/?page=/documentation/developer/ldapcsharp/cnet/data/front.html

祝大家假期快樂!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章