承接上篇,我們已建立 DSUser 領域物件與 UserAdmin DAL 骨架。本文將實作核心業務邏輯方法,並示範 ASP.NET 管理頁面的關鍵技術——Windows 身分模擬,解決 ASP.NET 執行帳號權限不足無法寫入 AD 的問題。
public DSUser LoadUser(string username) {
if (!connected) return null;
try {
// WinNT 提供者路徑格式: WinNT:///,user
string userPath = loginPath.Replace(",computer", "") + "/" + username + ",user";
DirectoryEntry userEntry = new DirectoryEntry(userPath, loginUsername, loginPassword, authType);
object native = userEntry.NativeObject; // 強制繫結驗證存在
return new DSUser(userEntry);
} catch (Exception ex) {
LogError("LoadUser failed: " + ex.Message);
return null; // 找不到或錯誤
}
}
public bool AddUserToGroup(string groupName, DSUser dsUser) {
if (!connected) return false;
try {
// 模擬高權限帳號執行 (見後續模擬章節)
string groupPath = loginPath.Replace(",computer", "") + "/" + groupName + ",group";
DirectoryEntry groupEntry = new DirectoryEntry(groupPath, loginUsername, loginPassword, authType);
groupEntry.Invoke("Add", new object[] { dsUser.userDirEntry.Path });
groupEntry.CommitChanges();
return true;
} catch (Exception ex) {
LogError("AddUserToGroup failed: " + ex.Message);
return false;
}
}
public ArrayList GetGroups() {
ArrayList groups = new ArrayList();
if (!connected) return groups;
try {
DirectoryEntry root = new DirectoryEntry(loginPath, loginUsername, loginPassword, authType);
foreach (DirectoryEntry child in root.Children) {
if (child.SchemaClassName == "Group") {
groups.Add(child.Name);
}
}
} catch (Exception ex) { LogError("GetGroups failed: " + ex.Message); }
return groups;
}
public bool DeleteUser(DSUser dsUser) {
if (!connected || dsUser.userDirEntry == null) return false;
try {
DirectoryEntry parent = dsUser.userDirEntry.Parent;
parent.Children.Remove(dsUser.userDirEntry);
return true;
} catch (Exception ex) {
LogError("DeleteUser failed: " + ex.Message);
return false;
}
}
public DirectoryEntry SaveUser(DSUser dsUser) {
if (!connected) return null;
try {
DirectoryEntry userEntry;
// 嘗試載入現有使用者
string userPath = loginPath.Replace(",computer", "") + "/" + dsUser.Username + ",user";
try {
userEntry = new DirectoryEntry(userPath, loginUsername, loginPassword, authType);
object native = userEntry.NativeObject; // 存在則更新
} catch {
// 不存在則建立新物件
DirectoryEntry parent = new DirectoryEntry(loginPath, loginUsername, loginPassword, authType);
userEntry = parent.Children.Add(dsUser.Username, "user");
}
// 寫入屬性 (WinNT 使用 Invoke)
userEntry.Invoke("Put", new object[] { "FullName", dsUser.FullName });
userEntry.Invoke("Put", new object[] { "Description", dsUser.Description });
// 密碼需透過 IADsUser::SetPassword (參考上篇範例)
userEntry.CommitChanges();
return userEntry;
} catch (Exception ex) {
LogError("SaveUser failed: " + ex.Message);
return null;
}
}
ASP.NET 預設以 ASPNET (IIS 5) 或 Network Service (IIS 6+) 執行,無 AD 寫入權限。解決方案:在程式碼區塊中暫時模擬具備 Domain Admin 或委派權限的帳號。
#region Impersonation Interop
public const int LOGON32_LOGON_INTERACTIVE = 2;
public const int LOGON32_PROVIDER_DEFAULT = 0;
[DllImport("advapi32.dll", CharSet = CharSet.Auto)]
public static extern int LogonUser(string lpszUserName, string lpszDomain,
string lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int DuplicateToken(IntPtr hToken, int impersonationLevel, ref IntPtr hNewToken);
#endregion
private WindowsImpersonationContext impersonationContext;
private bool ImpersonateValidUser(string userName, string domain, string password) {
IntPtr token = IntPtr.Zero, tokenDuplicate = IntPtr.Zero;
if (LogonUser(userName, domain, password, LOGON32_LOGON_INTERACTIVE,
LOGON32_PROVIDER_DEFAULT, ref token) != 0) {
if (DuplicateToken(token, 2, ref tokenDuplicate) != 0) { // SecurityImpersonation = 2
WindowsIdentity tempIdentity = new WindowsIdentity(tokenDuplicate);
impersonationContext = tempIdentity.Impersonate();
CloseHandle(token); CloseHandle(tokenDuplicate);
return true;
}
}
if (token != IntPtr.Zero) CloseHandle(token);
if (tokenDuplicate != IntPtr.Zero) CloseHandle(tokenDuplicate);
return false;
}
private void UndoImpersonation() {
if (impersonationContext != null) {
impersonationContext.Undo();
impersonationContext = null;
}
}
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private static extern bool CloseHandle(IntPtr handle);
protected void btnSave_Click(object sender, EventArgs e) {
// 1. 取得管理帳號憑證 (建議從安全設定檔讀取,勿硬編碼)
string adminUser = "adminUser"; string adminPwd = "P@ssw0rd"; string domain = "CORP";
if (ImpersonateValidUser(adminUser, domain, adminPwd)) {
try {
// 2. 在模擬內容下執行 DAL 寫入操作
UserAdmin dal = new UserAdmin(adminUser, adminPwd, AuthenticationTypes.Secure, domain);
DSUser user = new DSUser(txtUsername.Text, txtPassword.Text, domain);
user.FullName = txtFullName.Text;
user.Description = txtDescription.Text;
DirectoryEntry result = dal.SaveUser(user);
if (result != null) lblMsg.Text = "儲存成功!";
else lblMsg.Text = "失敗:" + dal.ErrorLog;
} finally {
// 3. 絕對要在 finally 區塊解除模擬
UndoImpersonation();
}
} else {
lblMsg.Text = "管理帳號模擬失敗,請檢查憑證與權限。";
}
}
為使模擬正常運作,需修改 %windir%\Microsoft.NET\Framework\ 中的 <processModel> 區段:
<processModel userName="SYSTEM" password="AutoGenerate" />
注意: 此設定影響整台伺服器所有 ASP.NET 應用程式,生產環境請謹慎評估安全性;替代方案為使用 <identity impersonate="true" userName="..." password="..." /> 在 web.config 設定應用程式層級模擬 (需 IIS 6+ 與特定權限)。
DSUser 屬性,支援編輯、刪除、新增。GetGroups(),提供 CheckBoxList 讓使用者勾選成員資格,送出時呼叫 AddUserToGroup/移除。dal.ErrorLog,記錄詳細堆疊至伺服器日誌。Create/Delete Group、群組巢狀管理,需透過 COM Interop 呼叫 IADsGroup 方法。DirectorySearcher 執行分頁查詢、索引屬性篩選,大幅提升大型目錄效能。System.DirectoryServices.Protocols (S.DS.P) 低階 LDAP 控制 TLS/SSL、SASL 驗證、分頁控制項。Microsoft.DirectoryServices.AccountManagement (System.DirectoryServices.AccountManagement, .NET 3.5+) 簡化測試模擬。透過 System.DirectoryServices 封裝 DAL、搭配 Windows 身分模擬,我們成功打造了一個可在 ASP.NET 中安全、高效操作 Active Directory 的完整方案。此架構將目錄操作複雜度隔離於 DAL,上層頁面專注於商務流程與 UI,符合關注點分離原則。未來遷移至 .NET Core / .NET 5+ 時,可改用 System.DirectoryServices.Protocols 或 Novell.Directory.Ldap.NETStandard 實現跨平台 LDAP 操作,核心 DAL 介面設計理念仍完全適用。
使用 msconfig「服務」索引標籤系統化排查 Windows 服務啟動衝突
Windows 2000 檔案系統選擇指南:FAT、FAT32、NTFS 特性、簇大小、相容性、轉換對照
Windows Server 2003 下 ASP 程式除錯常見錯誤與解決方案:瀏覽器顯示詳細錯誤、Access 連線問題
Windows 2000 群組原則管理完整教學:GPO 架構、安全性設定與實戰部署
Windows 2000 PPTP Ping 診斷工具實戰:Pptpclnt/Pptpsrv 雙端測試 GRE 封裝與 1723 埠連通性
解決 Windows Server 2008 與 Vista 顯示卡無法全開硬體加速的問題