做企業應用開發難免會跟 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
祝大家假期快樂!
微信公衆号:不止dotNET
作者: oec2003
出處: http://oec2003.cnblogs.com/
本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連結,否則 保留追究法律責任的權利。