安全的網(wǎng)站今日新聞最新頭條10條
前言
嗨,大家好!
在這個(gè)數(shù)據(jù)信息飛速發(fā)展的 21 世紀(jì),數(shù)據(jù)安全成為了每個(gè)企業(yè)關(guān)注的焦點(diǎn),保護(hù)企業(yè)數(shù)據(jù)安全日益成為企業(yè)工作中的重中之重。
域服務(wù)器,尤其是微軟的 Active Directory(AD),因其顯著的安全優(yōu)勢(shì),已成為不少企業(yè)的首選。
簡(jiǎn)單來(lái)說(shuō),域服務(wù)器就是一個(gè) “超級(jí)管理員”,它可以集中管理網(wǎng)絡(luò)中的所有用戶、計(jì)算機(jī)和其他資源。
通過(guò)域服務(wù)器,企業(yè)可以輕松地為用戶分配和管理權(quán)限,確保數(shù)據(jù)安全;也可以通過(guò)組策略,統(tǒng)一管理網(wǎng)絡(luò)中所有計(jì)算機(jī)的設(shè)置和安全策略,更好地保護(hù)敏感信息。
域服務(wù)器天然就是一個(gè)企業(yè)員工信息的數(shù)據(jù)庫(kù),將業(yè)務(wù)系統(tǒng)的身份鑒權(quán)跟域服務(wù)器緊密結(jié)合,無(wú)疑已經(jīng)成為安全技術(shù)發(fā)展的趨勢(shì)。
C# 擁有豐富的類庫(kù)來(lái)與 Active Directory(AD)互動(dòng),但使用時(shí)很不方便,因此我根據(jù)項(xiàng)目的實(shí)際業(yè)務(wù)需求,造了一個(gè)輪子,封裝了一些常用的操作 AD 的方法,簡(jiǎn)化了與 AD 的交互,用起來(lái)還挺方便的。
今天,我很高興與大家分享這些便利,希望能讓你的開(kāi)發(fā)之旅充滿樂(lè)趣和效率!
下面,讓我們一起來(lái)看看具體的實(shí)現(xiàn)步驟吧!
Step By Step 代碼
1. 創(chuàng)建配置文件
首先,需要?jiǎng)?chuàng)建一個(gè)配置文件如 LDAPConfig.config
,用于保存域的相關(guān)配置信息,內(nèi)容如下:
<?xml version="1.0" encoding="utf-8" ?>
<LDAPConfiguration><Host><URL>192.168.0.120:389</URL><LoginDN>CN=corp_test,CN=Users,DC=jacky,DC=com</LoginDN><Password>+6nkUhDs5lmcfMYS/qe7Qw==</Password></Host><UserSearch><SearchBase>OU=某某市軟件技術(shù)有限公司,DC=corp,DC=com</SearchBase><SearchFilter>(&(objectClass=Person)(sAMAccountName={0}))</SearchFilter><UserAttribute>sAMAccountName,memberOf,displayName</UserAttribute></UserSearch><AdminGroup>AndoErp_Administrators</AdminGroup>
</LDAPConfiguration>
2. 創(chuàng)建配置文件實(shí)體類
接下來(lái),創(chuàng)建一個(gè)配置文件實(shí)體類 LDAPConfigModel
,讀取和解析配置文件中的信息,留意注釋
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;
using Common.Util;namespace Common.Model
{[XmlRoot("LDAPConfiguration")]public class LDAPConfigModel{/// <summary>/// AD/LDAP 服務(wù)器和綁定帳號(hào)配置/// </summary>[XmlElement("Host")]public LdapHostSetting LdapHost { get; set; }/// <summary>/// 用戶搜索配置/// </summary>[XmlElement("UserSearch")]public UserSearchSetting UserSearch { get; set; }/// <summary>/// 管理員組/// </summary>public string AdminGroup { get; set; }}public class LdapHostSetting{/// <summary>/// AD/LDAP 服務(wù)器 URL/// </summary>public string URL { get; set; }/// <summary>/// 綁定帳號(hào)的 distinguished name/// </summary>public string LoginDN { get; set; }/// <summary>/// 綁定帳號(hào)的密碼(加密狀態(tài))/// </summary>public string Password { get; set; }/// <summary>/// 綁定帳號(hào)的密碼/// </summary>[XmlIgnore]public string SafePassword{get { return EncryptUtil.AESDecode(Password); }set { Password = EncryptUtil.AESEncode(value); }}}public class UserSearchSetting{/// <summary>/// 搜索路徑/// </summary>public string SearchBase { get; set; }/// <summary>/// 搜索過(guò)濾器/// </summary>public string SearchFilter { get; set; }/// <summary>/// 搜索屬性/// </summary>public string UserAttribute { get; set; }}
}
3. 創(chuàng)建一個(gè)域常用操作方法的封裝類
然后,創(chuàng)建一個(gè)名為 LdapUtil
的靜態(tài)類,封裝所有與域相關(guān)的操作方法,重點(diǎn):留意代碼注釋
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.DirectoryServices.Protocols;
using Common.Model;
using System.Net;
using Ando.ERP.Logger;
using System.IO;
using System.Reflection;namespace Common.Util
{/// <summary>/// 域 LDAP/AD 常用操作方法封裝類/// </summary>public static class LdapUtil{static readonly LDAPConfigModel ldapConfig = null;/// <summary>/// 靜態(tài)構(gòu)造方法,讀取配置文件,初始化 ldapConfig 對(duì)象/// </summary>static LdapUtil(){if (ldapConfig != null) return;string ldapConfigPath;if (AppDomain.CurrentDomain.SetupInformation.PrivateBinPath != null)ldapConfigPath = Path.Combine(AppDomain.CurrentDomain.SetupInformation.PrivateBinPath, "ConfigFile", "LDAPConfig.config");elseldapConfigPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ConfigFile", "LDAPConfig.config");if (!File.Exists(ldapConfigPath)){Assembly myAssembly = Assembly.GetExecutingAssembly();FileInfo dllFile = new FileInfo(myAssembly.Location);string path = dllFile.Directory.FullName;ldapConfigPath = Path.Combine(path, "ConfigFile", "LDAPConfig.config");}ldapConfig = ConfigUtil.Deserialize<LDAPConfigModel>(ldapConfigPath);}#region common private method/// <summary>/// 連接域服務(wù)器/// </summary>/// <param name="loginDN">域用戶</param>/// <param name="loginPassword">域用戶密碼</param>/// <param name="ldapConnection">LdapConnection 對(duì)象</param>private static void Connect(string loginDN, string loginPassword, ref LdapConnection ldapConnection){var networkCredential = new NetworkCredential(loginDN, loginPassword);ldapConnection.SessionOptions.SecureSocketLayer = false;ldapConnection.SessionOptions.ProtocolVersion = 3;ldapConnection.AuthType = AuthType.Basic;ldapConnection.Credential = networkCredential;ldapConnection.Bind();}/// <summary>/// <para>通過(guò) LDAP 配置信息連接域服務(wù)器</para>/// <para>如果參數(shù) cLoginDN 不為空,使用 cLoginDN 作為 loginDN</para>/// <para>如果參數(shù) cLoginPassword 不為空,使用 cLoginPassword 作為 loginPassword</para>/// </summary>/// <param name="ldapConfig"></param>/// <param name="ldapConnection"></param>/// <param name="cLoginDN"></param>/// <param name="cLoginPassword"></param>private static void Connect(ref LdapConnection ldapConnection, string cLoginDN = "", string cLoginPassword = ""){string loginDN = string.IsNullOrEmpty(cLoginDN) ? ldapConfig.LdapHost.LoginDN : cLoginDN;string loginPassword = string.IsNullOrEmpty(cLoginPassword) ? ldapConfig.LdapHost.SafePassword : cLoginPassword;AndoErpLogger.DEFAULT.DebugFormat("Connecting to LDAP/AD server [{0}] with account [{1}]", ldapConfig.LdapHost.URL, loginDN);Connect(loginDN, loginPassword, ref ldapConnection);}/// <summary>/// 通過(guò) Action 執(zhí)行一些自定義操作,LDAP Util 核心方法/// </summary>/// <param name="ldapConfig"></param>/// <param name="cLoginDN"></param>/// <param name="cLoginPassword"></param>/// <param name="func"></param>private static void LDAPCore(string cLoginDN, string cLoginPassword, Action<LdapConnection> action){LdapConnection ldapConnection = null;try{ldapConnection = new LdapConnection(ldapConfig.LdapHost.URL);Connect(ref ldapConnection, cLoginDN, cLoginPassword);action(ldapConnection);}catch{throw;}finally{if (ldapConnection != null) ldapConnection.Dispose();}}/// <summary>/// 通過(guò)用戶搜索配置搜索域數(shù)據(jù)/// </summary>/// <param name="ldapConnection"></param>/// <param name="sizeLimit">指定返回的實(shí)體數(shù)</param>/// <param name="searchPath">搜索路徑</param>/// <param name="filter">過(guò)濾字符串</param>/// <param name="isSubtree">是否深度搜索</param>/// <param name="attrs">搜索屬性</param>private static SearchResultEntryCollection Search(LdapConnection ldapConnection, int sizeLimit, string searchPath, string filter, params string[] attrs){try{SearchRequest request = new SearchRequest(searchPath, filter, SearchScope.Subtree, attrs);if (sizeLimit > 0) request.SizeLimit = sizeLimit;SearchResponse response = (SearchResponse)ldapConnection.SendRequest(request);return response.Entries;}catch (DirectoryOperationException e){// 返回此異常中已處理的所有數(shù)據(jù)// 因?yàn)?LDAP/AD 搜索默認(rèn)在 1000 以內(nèi),超過(guò)就會(huì)報(bào)這個(gè)錯(cuò)SearchResponse response = (SearchResponse)e.Response;return response.Entries;}catch (Exception ex){throw;}}#endregion#region business method/// <summary>/// 獲取指定域用戶的 distinguished name 值/// </summary>/// <param name="ldapConfig"></param>/// <param name="userName"></param>/// <returns></returns>private static LdpaUserInfo GetLDAPUserDN(string userName){var ldpaUser = new LdpaUserInfo();LDAPCore(null, null, (ldapConnection) =>{var userEntries = GetUserEntries(ldapConnection, userName, 50);var userEntry = userEntries[0];ldpaUser.UserDN = userEntry.DistinguishedName;ldpaUser.UserGroupList = GetAttributeValues(userEntry, "memberOf");ldpaUser.UserDisplayName = GetAttributeValues(userEntry, "displayName")[0];});return ldpaUser;}/// <summary>/// 獲取用戶搜索配置中的所有域帳戶實(shí)體/// </summary>/// <param name="ldapConfig"></param>/// <param name="ldapConnection"></param>/// <param name="userName"></param>/// <param name="sizeLimit"></param>/// <returns></returns>private static SearchResultEntryCollection GetUserEntries(LdapConnection ldapConnection, string userName, int sizeLimit){string userSearchBasePath = ldapConfig.UserSearch.SearchBase;string userSearchFilter = ldapConfig.UserSearch.SearchFilter;string[] userAttribute = ldapConfig.UserSearch.UserAttribute.Split(',');if (!string.IsNullOrEmpty(userName)){userSearchFilter = string.Format(userSearchFilter, userName);}var userEntries = Search(ldapConnection, sizeLimit, userSearchBasePath, userSearchFilter, userAttribute);if (userEntries == null || userEntries.Count == 0){string exceptionMsg = string.Format("沒(méi)有找到符合條件的域用戶,請(qǐng)檢查用戶搜索配置。搜索路徑: [{0}], 過(guò)濾條件: [{1}]", userSearchBasePath, userSearchFilter);throw new LdapException(exceptionMsg);}return userEntries;}/// <summary>/// 獲取域帳戶實(shí)體配置中的屬性的值/// </summary>/// <param name="entry"></param>/// <param name="attributeName"></param>/// <returns></returns>private static List<string> GetAttributeValues(SearchResultEntry entry, string attributeName){var attributes = entry.Attributes;var attributeObj = attributes[attributeName];if (attributeObj == null){return null;}List<string> valueList = new List<string>(); var attributeValues = attributes[attributeName].GetValues(typeof(string));foreach (var attributeValue in attributeValues){valueList.Add(CommonUtil.TranNull<string>(attributeValue));}return valueList;}/// <summary>/// 檢查登錄帳戶是否存在域中/// </summary>/// <param name="userName"></param>/// <param name="password"></param>/// <param name="ldapConfig"></param>/// <returns></returns>public static bool IsExistLDAPUser(string userName, string password, out bool isAdministrator, out string userDisplayName){bool result = false;bool userIsAdmin = false;string userShowName = string.Empty;try{var ldpaUser = GetLDAPUserDN(userName);string userDN = ldpaUser.UserDN;userShowName = ldpaUser.UserDisplayName;LDAPCore(userDN, password, (ldapConnection) =>{if (ldpaUser.UserGroupList == null || ldpaUser.UserGroupList.Count == 0)userIsAdmin = false;else{var findResult = ldpaUser.UserGroupList.First(x => x.IndexOf(ldapConfig.AdminGroup, StringComparison.OrdinalIgnoreCase) > 0);if (string.IsNullOrEmpty(findResult))userIsAdmin = false;elseuserIsAdmin = true;}result = true;});}catch (Exception ex){// TODO 可將錯(cuò)誤信息寫(xiě)到日志中,方便排查原因result = false;}isAdministrator = userIsAdmin;userDisplayName = userShowName;return result;}/// <summary>/// 獲取用戶搜索配置中的所有域帳戶的 displayName 的值/// </summary>/// <param name="ldapConfig"></param>/// <param name="userName"></param>/// <returns></returns>public static List<string> GetAllUsers(){var list = new List<string>();LDAPCore(null, null, (ldapConnection) =>{var userEntries = GetUserEntries(ldapConnection, "*", 350);foreach (var userEntry in userEntries){var userDisplayName = GetAttributeValues((SearchResultEntry)userEntry, "displayName")[0];list.Add(userDisplayName);}});return list;}#endregion#region Inner class/// <summary>/// 域用戶基本信息/// </summary>sealed class LdpaUserInfo{public string UserDN { get; set; }public List<string> UserGroupList { get; set; }public string UserDisplayName { get; set; }}#endregion}
}
4. 使用示例
最后,我們來(lái)看一下如何使用這個(gè)封裝類來(lái)執(zhí)行一些基本操作,比如登錄
/// <summary>
/// 登錄
/// </summary>
/// <param name="userName"></param>
/// <param name="password"></param>
/// <returns></returns>
public void Login(string userName, string password)
{var hasUser = LdapUtil.IsExistLDAPUser(userName, password, out bool isAdministrator, out string userDisplayName);if (hasUser){// 域用戶存在,登錄成功,繼續(xù)處理后續(xù)業(yè)務(wù)}else{// 域用戶不存在,登錄失敗}
}
總結(jié)
好了,今天的分享就到這里啦!
通過(guò)以上的封裝,我們可以更高效地與 Active Directory 進(jìn)行交互,無(wú)論是用戶的身份驗(yàn)證、信息查詢,還是其他操作,這些方法都能幫助簡(jiǎn)化代碼,提高開(kāi)發(fā)效率,你可以把它用在自己的項(xiàng)目里,根據(jù)自己的實(shí)際業(yè)務(wù)需求,繼續(xù)添加新的業(yè)務(wù)處理方法或刪減其中一些方法!
隨著數(shù)字化進(jìn)程的加速,越來(lái)越多的企業(yè)轉(zhuǎn)向域服務(wù)器來(lái)高效管理網(wǎng)絡(luò)環(huán)境,C# 的靈活性和強(qiáng)大功能使其與 Active Directory 的結(jié)合成為了一種自然的選擇,希望這篇教程能夠給你提供一些實(shí)用的操作方法和思路。
最后,如果你有更好的想法或建議,歡迎留言討論!
往期精彩
- 閑話 .NET(7):.NET Core 能淘汰 .NET FrameWork 嗎?
- 常用的 4 種 ORM 框架(EF Core,SqlSugar,FreeSql,Dapper)對(duì)比總結(jié)
我是老楊,一個(gè)執(zhí)著于編程樂(lè)趣、至今奮斗在一線的 10年+ 資深研發(fā)老鳥(niǎo),是軟件項(xiàng)目管理師,也是快樂(lè)的程序猿,持續(xù)免費(fèi)分享全棧實(shí)用編程技巧、項(xiàng)目管理經(jīng)驗(yàn)和職場(chǎng)成長(zhǎng)心得!歡迎關(guān)注老楊的公眾號(hào),更多干貨等著你!