C# Windows AD 驗證示範

現在網路上大多的文章都是 .NET Framework 的 Windows Active Directory 驗證教學和示範,很少 .NET Core 5 或是 .NET 6 以上的資訊,筆者在串接 AD 驗證的時候一直遇到問題,將此解決過程記錄下來,希望能夠幫助到有需要的人

以下正文:

先使用 NuGet 安裝 System.DirectoryServices.AccountManagement 套件,或是使用 .NET CLI 執行以下指令安裝
	
dotnet add package System.DirectoryServices.AccountManagement
    

錯誤示範:
筆者在測試的時候一直使用下面的程式碼進行測試,一直拋出錯誤
    
string domain = "192.168.0.100";
string username = "ruyut";
string password = "Password123";

using PrincipalContext context = new PrincipalContext(ContextType.Domain, domain);

bool exist = context.ValidateCredentials(username, password);
Console.WriteLine(exist ? "登入成功" : "登入失敗"); // 登入成功

Console.WriteLine($"contextName: {context.Name}"); // contextName: 192.168.0.100
Console.WriteLine($"context.UserName: {context.UserName}"); // context.UserName:

UserPrincipal user = UserPrincipal.FindByIdentity(context, username); // 拋出錯誤
    

錯誤訊息如下:
    
Unhandled exception. System.DirectoryServices.DirectoryServicesCOMException (0x8007052E): 使用者名稱或密碼不正確。
   at System.DirectoryServices.DirectoryEntry.Bind(Boolean throwIfFail)
   at System.DirectoryServices.DirectoryEntry.Bind()
   at System.DirectoryServices.DirectoryEntry.get_AdsObject()
   at System.DirectoryServices.PropertyValueCollection.PopulateList()
   at System.DirectoryServices.PropertyValueCollection..ctor(DirectoryEntry entry, String propertyName)
   at System.DirectoryServices.PropertyCollection.get_Item(String propertyName)
   at System.DirectoryServices.AccountManagement.PrincipalContext.DoLDAPDirectoryInitNoContainer()
   at System.DirectoryServices.AccountManagement.PrincipalContext.DoDomainInit()
   at System.DirectoryServices.AccountManagement.PrincipalContext.Initialize()
   at System.DirectoryServices.AccountManagement.PrincipalContext.get_QueryCtx()
   at System.DirectoryServices.AccountManagement.Principal.FindByIdentityWithTypeHelper(PrincipalContext context, Type principalType, Nullable`1 identityType, String identityValue, DateTime refDate)
   at System.DirectoryServices.AccountManagement.Principal.FindByIdentityWithType(PrincipalContext context, Type principalType, String identityValue)
   at System.DirectoryServices.AccountManagement.UserPrincipal.FindByIdentity(PrincipalContext context, String identityValue)
   at Program.<Main>$(String[] args) in C:\Users\ruyut\Documents\RiderProjects\2022\Test\ConsoleAppAdTest\ConsoleAppAdTest\Program.cs:line 13

    

奇怪,明明在第 6 行的時候就是登入成功,那為什麼第 13 行會出現「使用者名稱或密碼不正確」呢?又再建了一個新帳號,怕實際不能登入,下載 Active Directory Explorer 做測試,確實可以登入,使用錯誤的密碼第 6 行也確實會失敗。離開公司時邊走路邊想,邊騎車邊想,邊吃晚餐邊想... 啊!為什麼第 13 行只需要帳號不需要密碼,該不會這裡說的是 context 的使用者名稱或密碼錯誤吧?!
看了一下文件,還真的有多載...

再不看文件阿...
調整後 context.UserName 也讀的到了, UserPrincipal 也有東西,終於可以睡覺了!
    
string domain = "192.168.0.100";
string username = "ruyut";
string password = "Password123";

using PrincipalContext context = new PrincipalContext(ContextType.Domain, domain, username, password);

bool exist = context.ValidateCredentials(username, password);
Console.WriteLine(exist ? "登入成功" : "登入失敗"); // 登入成功

Console.WriteLine($"contextName: {context.Name}"); // contextName: 192.168.0.100
Console.WriteLine($"context.UserName: {context.UserName}"); // context.UserName: ruyut@ruyut.com

UserPrincipal user = UserPrincipal.FindByIdentity(context, username);
    

常用資料整理:
    
Console.WriteLine($"使用者名稱: {user.Name}"); // 使用者名稱: R
Console.WriteLine($"使用者顯示名稱: {user.DisplayName}"); // 使用者顯示名稱: Ruyut
Console.WriteLine($"描述: {user.Description}"); // 描述: 測試描述
Console.WriteLine($"Email: {user.EmailAddress}"); // Email: 測試電子郵件

DirectoryEntry? directoryEntry = user.GetUnderlyingObject() as DirectoryEntry;
Console.WriteLine($"公司: {directoryEntry?.Properties["company"].Value}"); // 公司: 測試公司
Console.WriteLine($"部門: {directoryEntry?.Properties["department"].Value}"); // 部門: 測試部門
Console.WriteLine($"職稱: {directoryEntry?.Properties["title"].Value}"); // 職稱: 測試職稱
Console.WriteLine($"電話號碼: {directoryEntry?.Properties["telephonenumber"].Value}"); // 電話號碼: 測試電話號碼
Console.WriteLine($"辦公室: {directoryEntry?.Properties["physicaldeliveryofficename"].Value}"); // 辦公室: 測試辦公室
Console.WriteLine($"網頁: {directoryEntry?.Properties["wwwhomepage"].Value}"); // 網頁: 測試網頁
    

AD 使用者 - 一般

AD 使用者 - 組織

順便附上查看所有使用者屬性資料的方式:
    
var searcher = new DirectorySearcher(user.GetUnderlyingObject() as DirectoryEntry);
foreach (SearchResult result in searcher.FindAll())
{
    foreach (string propertyName in result.Properties.PropertyNames)
    {
        foreach (object propertyValue in result.Properties[propertyName])
        {
            Console.WriteLine($"{propertyName} : {propertyValue}");
        }
    }
}
    


參考資料:
Microsoft.Learn - PrincipalContext Constructors
Microsoft.Learn - UserPrincipal Class

留言