完成後 ADパスワードを検証するためのAPI 後,次に、パスワードを変更する部分を書いてみましょう,つまずきながらも、ようやく完成しました。。今回は3つの異なるパッケージの書き方を紹介します。,そしてタイトルにも触れますが、 “AD パスワードを変更する” 書いてる途中で気づいたので,一部の方法では、実行するにはドメイン管理者の権限が必要です。,したがって、この記事の範囲は、ユーザー自身のアカウントとパスワードの使用のみに限定されます。,パスワードの変更操作を完了できます。
"スイート"
一般的に言えば,AD の変更には 3 種類のパッケージが使用される場合があります。:
- System.DirectoryServices.Protocols (S.DS.P)
- System.DirectoryServices (S.DS)
- System.DirectoryServices.AccountManagement (S.DS.AM)
3つのキットの比較:
- 運用レベル:S.DS.AM > S.DS > S.DS.P
- 複雑:S.DS.P > S.DS > S.DS.AM
- 柔軟性:S.DS.P > S.DS > S.DS.AM
- スピード:S.DS.P > S.DS > S.DS.AM
另外,S.DS.P は LDAP プロトコルに基づく低レベルの操作であるため、,なので互換性も高いです,OpenLDAPの場合、ADサポートも問題なし,ただし、S.DS と S.DS.AM はさらに制限される可能性があります。,Microsoft 向けの広告 (Active Directory)。
サービス/PasswordManagementService.cs
using AD.Models; using Microsoft.Extensions.Options; using System.DirectoryServices; using System.DirectoryServices.Protocols; using System.Net; using System.Text; using System.DirectoryServices.AccountManagement; using System.Runtime.Versioning; namespace AD.Services { public class PasswordManagementService(IOptions<LdapSettings> ldapSettings) { private readonly string _ldapServer = ldapSettings.Value.Server; private readonly string _domain = ldapSettings.Value.Domain; private readonly string _baseDn = ldapSettings.Value.BaseDn; // 修改密碼 (透過 System.DirectoryServices.Protocols) public bool ChangePasswordSdsP(string username, string oldPassword, string newPassword) { try { using var connection = new LdapConnection(new LdapDirectoryIdentifier(_ldapServer, 636)); // 修改密碼必須使用 LDAPS 636 port。 connection.Credential = new NetworkCredential(username, oldPassword, _domain); connection.SessionOptions.SecureSocketLayer = true; // 修改密碼必須使用 LDAPS。 connection.AuthType = AuthType.Kerberos; // 如果使用 Negotiate 會先嘗試 Kerberos,失敗再改試 NTLM。 connection.Bind(); // 嘗試綁定,成功表示驗證通過 // 以 sAMAccountName 查詢使用者的 DN SearchRequest searchRequest = new( _baseDn, // 根目錄 $"(sAMAccountName={username})", // 根據 sAMAccountName 查詢 System.DirectoryServices.Protocols.SearchScope.Subtree, "distinguishedName" // 只獲取 DN 屬性 ); SearchResponse searchResponse = (SearchResponse)connection.SendRequest(searchRequest); if (searchResponse.Entries.Count == 0) { Console.WriteLine("User not found."); return false; } string userDn = searchResponse.Entries[0].DistinguishedName; // 使用 LDAP 修改密碼屬性 (unicodePwd) 時, // 只有高權限帳號 (如 Domain Admins) 可以對 unicodePwd 屬性執行修改 (DirectoryAttributeOperation.Replace)。 // 若要讓一般使用者更改自己的密碼,必須透過 LDAPS (安全通道),且同時送出 delete 與 add 操作來替換密碼。 var deleteOldPassword = new DirectoryAttributeModification { Operation = DirectoryAttributeOperation.Delete, Name = "unicodePwd" }; deleteOldPassword.Add(Encoding.Unicode.GetBytes($"\"{oldPassword}\"")); var addNewPassword = new DirectoryAttributeModification { Operation = DirectoryAttributeOperation.Add, Name = "unicodePwd" }; addNewPassword.Add(Encoding.Unicode.GetBytes($"\"{newPassword}\"")); // 組合 ModifyRequest,執行 Delete + Add 操作 var request = new ModifyRequest( userDn, deleteOldPassword, addNewPassword ); connection.SendRequest(request); return true; } catch (Exception ex) { Console.WriteLine($"Error: {ex.Message}"); return false; } } // 修改密碼 (透過 System.DirectoryServices) [SupportedOSPlatform("windows")] // 宣告以下方法僅適用於 Windows,避免 PrincipalContext 等 API 被提示要留意跨平臺問題。 public bool ChangePasswordSds(string username, string oldPassword, string newPassword) { try { // 使用 DirectorySearcher 以 sAMAccountName 查詢使用者的 DN DirectorySearcher searcher = new(new DirectoryEntry($"LDAP://{_baseDn}")); searcher.Filter = $"(sAMAccountName={username})"; searcher.PropertiesToLoad.Add("distinguishedName"); SearchResult result = searcher.FindOne(); if (result != null) { string userDn = result.Properties["distinguishedName"][0].ToString(); using DirectoryEntry user = new($"LDAP://{userDn}", username, oldPassword); user.Invoke("ChangePassword", [oldPassword, newPassword]); user.CommitChanges(); return true; } else { Console.WriteLine("User not found."); return false; } } catch (Exception ex) { Console.WriteLine($"Error: {ex.Message}"); return false; } } // 修改密碼 (透過 System.DirectoryServices.AccountManagement) [SupportedOSPlatform("windows")] // 宣告以下方法僅適用於 Windows,避免 PrincipalContext 等 API 被提示要留意跨平臺問題。 public bool ChangePasswordSdsAm(string username, string oldPassword, string newPassword) { try { using var context = new PrincipalContext(ContextType.Domain, _domain); // 驗證舊密碼是否正確 if (!context.ValidateCredentials(username, oldPassword)) { throw new UnauthorizedAccessException("用戶名或舊密碼不正確!"); } // 使用 UserPrincipal 修改密碼 using var user = UserPrincipal.FindByIdentity(context, username) ?? throw new Exception("找不到指定的使用者!"); user.ChangePassword(oldPassword, newPassword); return true; } catch (Exception ex) { Console.WriteLine($"修改密碼時發生錯誤: {ex.Message}"); return false; } } } }
コントローラ/PasswordManagementController.cs
using AD.Models; using AD.Services; using Microsoft.AspNetCore.Mvc; using System.Runtime.Versioning; namespace AD.Controllers { [Route("Password")] [ApiController] public class PasswordManagementController(PasswordManagementService passwordManagement) : ControllerBase { // 修改密碼 (透過 System.DirectoryServices.Protocols) [HttpPost("ChangeSdsP")] public IActionResult ChangeSdsP([FromBody] PasswordChangeRequest request) { if (passwordManagement.ChangePasswordSdsP(request.Username, request.OldPassword, request.NewPassword)) { return Ok("密碼修改成功。"); } return BadRequest("密碼修改失敗。"); } // 修改密碼 (透過 System.DirectoryServices) [HttpPost("ChangeSds")] [SupportedOSPlatform("windows")] // 宣告以下用到的方法僅適用於 Windows,避免 ChangePassword 方法被提示要留意跨平臺問題。 public IActionResult ChangeSds([FromBody] PasswordChangeRequest request) { if (passwordManagement.ChangePasswordSds(request.Username, request.OldPassword, request.NewPassword)) { return Ok("密碼修改成功。"); } return BadRequest("密碼修改失敗。"); } // 修改密碼 (透過 System.DirectoryServices.AccountManagement) [HttpPost("ChangeSdsAm")] [SupportedOSPlatform("windows")] // 宣告以下用到的方法僅適用於 Windows,避免 ChangePassword 方法被提示要留意跨平臺問題。 public IActionResult ChangeSdsAm([FromBody] PasswordChangeRequest request) { if (passwordManagement.ChangePasswordSdsAm(request.Username, request.OldPassword, request.NewPassword)) { return Ok("密碼修改成功。"); } return BadRequest("密碼修改失敗。"); } } }
モデル/AuthRequests.cs
namespace AD.Models { public class PasswordChangeRequest { public string Username { get; set; } // 使用者帳戶 public string OldPassword { get; set; } // 舊密碼 public string NewPassword { get; set; } // 新密碼 } }
モデル/LdapSettings.cs
namespace AD.Models { public class LdapSettings { public string Server { get; set; } = string.Empty; public string Domain { get; set; } = string.Empty; public string BaseDn { get; set; } = string.Empty; } }
アプリ設定.json
{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } }, "AllowedHosts": "*", "LdapSettings": { "Server": "dc1.abc.com.tw", // 如果是用 Kerberos 驗證,AD 的伺服器不可以使用 IP。 "Domain": "abc.com.tw", "BaseDn": "DC=abc,DC=com,DC=tw" } }
プログラム.cs
using AD.Models; using AD.Services; var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllers(); builder.Services.Configure<LdapSettings>(builder.Configuration.GetSection("LdapSettings")); // 讀取 appsettings.json 的 LdapSettings 資料。 builder.Services.AddScoped<PasswordManagementService>(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); var app = builder.Build(); // Configure the HTTP request pipeline. // 讓 Swagger 只在開發環境時使用。 if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); app.UseAuthorization(); app.MapControllers(); app.Run();
テスト中,Swagger の使用をお勧めします,もっと便利になりますよ。
最後,DC サーバーは LDAPS をサポートしていることを確認する必要があります,とは別に 636 pingを送信する,DC による認証情報のインストールも手伝う必要があります。,DC で ldp.exe ツールを使用して確認する方がより正確です。。另外,テストアカウントの「ユーザーはパスワードを変更できない」がチェックされていないことを必ず確認してください。,数日間ここに閉じ込められている,チェックを外すのを忘れていたことにやっと気づきました…。
「参考リンク」