완료 후 AD 비밀번호 확인을 위한 API 후,그런 다음 비밀번호를 변경하는 부분을 작성해 보세요.,헤매다가도 드디어 완성했어요.。이번에는 세 가지 패키지를 작성하는 방법을 공유하겠습니다.,그리고 제목에서 언급하겠습니다 “AD 비밀번호 변경” 집필 과정에서 발견했기 때문이에요,일부 방법을 수행하려면 도메인 관리자의 권한이 필요합니다.,따라서 이 글의 범위는 사용자 자신의 계정과 비밀번호만을 사용하는 것으로 제한됩니다.,비밀번호 변경 작업을 완료할 수 있습니다.。
"모음곡"
일반적으로 말하면,AD를 수정하는 데 사용되는 세 가지 유형의 패키지가 있을 수 있습니다.:
- 시스템.디렉토리서비스.프로토콜 (S.DS.P)
- 시스템.디렉토리서비스 (SDS)
- 시스템.디렉토리서비스.계정관리 (S.DS.AM)
세 가지 키트 비교:
- 운영 수준: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은 더 제한적일 수 있습니다.,마이크로소프트용 AD (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" } }
Program.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 핑하려면,또한 DC에서 자격 증명을 설치하도록 도와야 합니다.,DC에서 ldp.exe 도구를 사용하여 확인하는 것이 더 정확합니다.。另外,테스트 계정에서 "사용자는 비밀번호를 변경할 수 없습니다."가 선택되어 있지 않은지 확인하세요.,나는 며칠 동안 여기에 갇혀 있었어요,마침내 나는 선택을 취소하는 것을 잊었다는 것을 깨달았습니다 ...。
"참고링크"